diff options
949 files changed, 30150 insertions, 10240 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 433cd50bdc13..6d74a840525b 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -14,7 +14,9 @@ aconfig_srcjars = [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.companion.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", + ":android.content.res.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", @@ -32,10 +34,13 @@ aconfig_srcjars = [ ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", ":android.widget.flags-aconfig-java{.generated_srcjars}", + ":com.android.media.audio.flags-aconfig-java{.generated_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}", @@ -43,7 +48,9 @@ aconfig_srcjars = [ ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":aconfig_midi_flags_java_lib{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -293,6 +300,26 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-host", + aconfig_declarations: "android.content.pm.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + +// Resources +aconfig_declarations { + name: "android.content.res.flags-aconfig", + package: "android.content.res", + srcs: ["core/java/android/content/res/*.aconfig"], +} + +java_aconfig_library { + name: "android.content.res.flags-aconfig-java", + aconfig_declarations: "android.content.res.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", @@ -306,6 +333,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Media Audio +java_aconfig_library { + name: "com.android.media.audio.flags-aconfig-java", + aconfig_declarations: "aconfig_audio_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Permissions aconfig_declarations { name: "android.permission.flags-aconfig", @@ -317,6 +351,24 @@ java_aconfig_library { name: "android.permission.flags-aconfig-java", aconfig_declarations: "android.permission.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.permission", + ], + +} + +// 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 @@ -339,6 +391,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", @@ -436,7 +494,7 @@ aconfig_declarations { package: "android.service.autofill", srcs: [ "services/autofill/bugfixes.aconfig", - "services/autofill/features.aconfig" + "services/autofill/features.aconfig", ], } @@ -445,3 +503,23 @@ java_aconfig_library { aconfig_declarations: "android.service.autofill.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Companion +aconfig_declarations { + name: "android.companion.flags-aconfig", + package: "android.companion", + srcs: ["core/java/android/companion/*.aconfig"], +} + +java_aconfig_library { + name: "android.companion.flags-aconfig-java", + 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/Android.bp b/Android.bp index a507465aa419..0c199a634725 100644 --- a/Android.bp +++ b/Android.bp @@ -227,7 +227,6 @@ java_library { "android.hardware.radio.messaging-V3-java", "android.hardware.radio.modem-V3-java", "android.hardware.radio.network-V3-java", - "android.hardware.radio.satellite-V1-java", "android.hardware.radio.sim-V3-java", "android.hardware.radio.voice-V3-java", "android.hardware.thermal-V1.0-java-constants", diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index bded26a8748f..015487d20f8d 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -25,6 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES} +ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} diff --git a/Ravenwood.bp b/Ravenwood.bp index 9218cc9bc3f8..da02298b7415 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -59,6 +59,7 @@ java_genrule_host { // Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules. java_genrule_host { name: "framework-minus-apex.ravenwood", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-minus-apex.ravenwood-base{ravenwood.jar}", @@ -66,5 +67,4 @@ java_genrule_host { out: [ "framework-minus-apex.ravenwood.jar", ], - visibility: ["//visibility:public"], } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index f1403bd51049..e833bb95a302 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -439,7 +439,7 @@ public class JobParameters implements Parcelable { * provides an easy way to tell whether the job is being executed due to the deadline * expiring. Note: If the job is running because its deadline expired, it implies that its * constraints will not be met. However, - * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever + * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever * run when their constraints are satisfied, therefore, the constraints will still be satisfied * for a periodic job even if the deadline has expired. */ diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 4e3cb7d83451..3e835b8ad429 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.job" flag { name: "relax_prefetch_connectivity_constraint_only_on_charger" - namespace: "backstagepower" + namespace: "backstage_power" description: "Only relax a prefetch job's connectivity constraint when the device is charging" bug: "299329948" }
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 3cbee5d07bb9..384a480af4e9 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -173,8 +173,6 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import dalvik.annotation.optimization.NeverCompile; -import libcore.util.EmptyArray; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -260,7 +258,7 @@ public class AlarmManagerService extends SystemService { /** * A map from uid to the last op-mode we have seen for * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change - * when the denylist changes. + * when the app-op changes. */ @VisibleForTesting @GuardedBy("mLock") @@ -671,9 +669,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting final class Constants implements DeviceConfig.OnPropertiesChangedListener, EconomyManagerInternal.TareStateChangeListener { - @VisibleForTesting - static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; - // Key names stored in the settings value. @VisibleForTesting static final String KEY_MIN_FUTURITY = "min_futurity"; @@ -727,8 +722,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay"; @VisibleForTesting - static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list"; - @VisibleForTesting static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz"; @VisibleForTesting static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz"; @@ -835,13 +828,6 @@ public class AlarmManagerService extends SystemService { public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY; /** - * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for - * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can - * be accessed without synchronizing on {@link #mLock}. - */ - public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet(); - - /** * Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent * WAKE_FROM_IDLE alarm. */ @@ -1025,21 +1011,6 @@ public class AlarmManagerService extends SystemService { PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY, DEFAULT_PRIORITY_ALARM_DELAY); break; - case KEY_EXACT_ALARM_DENY_LIST: - final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST, - ""); - final String[] values = rawValue.isEmpty() - ? EmptyArray.STRING - : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1); - if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) { - Slog.w(TAG, "Deny list too long, truncating to " - + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements."); - updateExactAlarmDenyList( - Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE)); - } else { - updateExactAlarmDenyList(values); - } - break; case KEY_MIN_DEVICE_IDLE_FUZZ: case KEY_MAX_DEVICE_IDLE_FUZZ: if (!deviceIdleFuzzBoundariesUpdated) { @@ -1110,28 +1081,6 @@ public class AlarmManagerService extends SystemService { } } - private void updateExactAlarmDenyList(String[] newDenyList) { - final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList)); - final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST); - final Set<String> added = new ArraySet<>(newDenyList); - - added.removeAll(EXACT_ALARM_DENY_LIST); - removed.removeAll(newSet); - if (added.size() > 0) { - mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added) - .sendToTarget(); - } - if (removed.size() > 0) { - mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed) - .sendToTarget(); - } - if (newDenyList.length == 0) { - EXACT_ALARM_DENY_LIST = Collections.emptySet(); - } else { - EXACT_ALARM_DENY_LIST = newSet; - } - } - private void updateDeviceIdleFuzzBoundaries() { final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_ALARM_MANAGER, @@ -1277,9 +1226,6 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw); pw.println(); - pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST); - pw.println(); - pw.print(KEY_MIN_DEVICE_IDLE_FUZZ); pw.print("="); TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw); @@ -2114,14 +2060,10 @@ public class AlarmManagerService extends SystemService { ? permissionState : (newMode == AppOpsManager.MODE_ALLOWED); } else { - final boolean allowedByDefault = - !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT) - ? allowedByDefault - : (oldMode == AppOpsManager.MODE_ALLOWED); + || (oldMode == AppOpsManager.MODE_ALLOWED); hasPermission = (newMode == AppOpsManager.MODE_DEFAULT) - ? allowedByDefault - : (newMode == AppOpsManager.MODE_ALLOWED); + || (newMode == AppOpsManager.MODE_ALLOWED); } if (hadPermission && !hasPermission) { @@ -2769,11 +2711,8 @@ public class AlarmManagerService extends SystemService { // Compatibility permission check for older apps. final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid, packageName); - if (mode == AppOpsManager.MODE_DEFAULT) { - hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); - } else { - hasPermission = (mode == AppOpsManager.MODE_ALLOWED); - } + hasPermission = (mode == AppOpsManager.MODE_DEFAULT) + || (mode == AppOpsManager.MODE_ALLOWED); } mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start); return hasPermission; @@ -3993,63 +3932,6 @@ public class AlarmManagerService extends SystemService { } /** - * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that - * either got added or deleted. - * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission. - * - * Note that these packages don't need to be installed on the device, but if they are and they - * do undergo a permission change, we will handle them appropriately. - * - * This should not be called with the lock held as it calls out to other services. - * This is not expected to get called frequently. - */ - void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) { - Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from") - + " the exact alarm deny list."); - - final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); - - for (int i = 0; i < changedPackages.size(); i++) { - final String changedPackage = changedPackages.valueAt(i); - for (final int userId : startedUserIds) { - final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId); - if (uid <= 0) { - continue; - } - if (!isExactAlarmChangeEnabled(changedPackage, userId)) { - continue; - } - if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) { - continue; - } - if (hasUseExactAlarmInternal(changedPackage, uid)) { - continue; - } - if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { - // Permission isn't requested, deny list doesn't matter. - continue; - } - final int appOpMode; - synchronized (mLock) { - appOpMode = mLastOpScheduleExactAlarm.get(uid, - AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)); - } - if (appOpMode != AppOpsManager.MODE_DEFAULT) { - // Deny list doesn't matter. - continue; - } - // added: true => package was added to the deny list - // added: false => package was removed from the deny list - if (added) { - removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true); - } else { - sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); - } - } - } - } - - /** * Called when an app loses the permission to use exact alarms. This will happen when the app * no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or * {@link Manifest.permission#USE_EXACT_ALARM}. @@ -4931,8 +4813,8 @@ public class AlarmManagerService extends SystemService { public static final int CHARGING_STATUS_CHANGED = 6; public static final int REMOVE_FOR_CANCELED = 7; public static final int REMOVE_EXACT_ALARMS = 8; - public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9; - public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; + // Unused id 9 + // Unused id 10 public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; @@ -5041,12 +4923,6 @@ public class AlarmManagerService extends SystemService { String packageName = (String) msg.obj; removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true); break; - case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: - handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); - break; - case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED: - handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false); - break; case REFRESH_EXACT_ALARM_CANDIDATES: refreshExactAlarmCandidates(); break; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 2b7438c862bd..fdeb072cacb3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -536,10 +536,13 @@ public class PrefetchController extends StateController { static final String KEY_LAUNCH_TIME_ALLOWANCE_MS = PC_CONSTANT_PREFIX + "launch_time_allowance_ms"; - private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS; - private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS; - /** How much time each app will have to run jobs within their standby bucket window. */ + /** + * The earliest amount of time before the next estimated app launch time that we may choose + * to run a prefetch job for the app. + */ public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS; /** diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 30b442336148..e1621008cc33 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -124,7 +124,6 @@ droidstubs { "packages/modules/Media/apex/aidl/stable", ], }, - extensions_info_file: ":sdk-extensions-info", } droidstubs { @@ -132,13 +131,7 @@ droidstubs { defaults: ["framework-doc-stubs-sources-default"], args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", - api_levels_annotations_enabled: true, - api_levels_annotations_dirs: [ - "sdk-dir", - "api-versions-jars-dir", - ], - api_levels_sdk_type: "system", - extensions_info_file: ":sdk-extensions-info", + api_levels_module: "api_versions_system", } ///////////////////////////////////////////////////////////////////// diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 2d9c988556ec..7e41660cf1a2 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: { @@ -675,6 +695,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -690,6 +711,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -707,6 +729,7 @@ java_api_library { "test-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -722,6 +745,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", ], + enable_validation: false, } java_api_library { @@ -741,6 +765,7 @@ java_api_library { "module-lib-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -754,6 +779,32 @@ java_api_library { "stub-annotations", ], visibility: ["//visibility:public"], + enable_validation: false, +} + +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", + ], + enable_validation: false, } java_api_library { @@ -770,6 +821,7 @@ java_api_library { "android_module_lib_stubs_current.from-text", ], visibility: ["//visibility:public"], + enable_validation: false, } //////////////////////////////////////////////////////////////////////// diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 2cc5078c8844..29a8dfa96a57 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -1,68 +1,4 @@ -android/adservices/ondevicepersonalization/DownloadCompletedInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedInput [101] -android/adservices/ondevicepersonalization/DownloadCompletedOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedOutput [101] -android/adservices/ondevicepersonalization/EventLogRecord.java:13: lint: Unresolved link/see tag "RequestRecordRecord" in android.adservices.ondevicepersonalization.EventLogRecord [101] -android/adservices/ondevicepersonalization/EventUrlProvider.java:43: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onEvent IsolatedWorker#onEvent" in android.adservices.ondevicepersonalization.EventUrlProvider [101] -android/adservices/ondevicepersonalization/ExecuteInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteInput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute() OnDevicePersonalizationManager#execute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101] -android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101] -android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101] -android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101] -android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101] -android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101] -android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101] -android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101] -android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101] -android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedService#onExecute() IsolatedService#onExecute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:19: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView#getHostToken()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "execute" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "#execute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:64: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:69: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:70: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/RenderInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderInput [101] -android/adservices/ondevicepersonalization/RenderInput.java:53: lint: Unresolved link/see tag "onExecute" in android.adservices.ondevicepersonalization.RenderInput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#requestSurfacePackage() OnDevicePersonalizationManager#requestSurfacePackage()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:41: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:52: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:114: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:127: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:33: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:85: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig.Builder [101] -android/adservices/ondevicepersonalization/RequestLogRecord.java:19: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RequestLogRecord [101] -android/adservices/ondevicepersonalization/SurfacePackageToken.java:20: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.SurfacePackageToken [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101] -android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101] -android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101] -android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101] -android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101] -android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101] -android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101] -android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101] +// b/305195721 android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101] @@ -71,6 +7,8 @@ android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag " android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101] + +// b/303477132 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101] @@ -83,40 +21,8 @@ android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Featur android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101] -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/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101] -android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101] -android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101] -android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101] -android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101] -android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101] -android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [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] + +// b/303582215 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] android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101] @@ -142,162 +48,37 @@ android/hardware/camera2/CaptureRequest.java:704: lint: Unresolved link/see tag android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101] android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101] android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101] -android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101] -android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101] -android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101] -android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101] -android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANT AttributeSdkUsage#USAGE_ASSISTANT" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_GAME AttributeSdkUsage#USAGE_GAME" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_MEDIA AttributeSdkUsage#USAGE_MEDIA" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_EVENT AttributeSdkUsage#USAGE_NOTIFICATION_EVENT" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_UNKNOWN AttributeSdkUsage#USAGE_UNKNOWN" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION AttributeSdkUsage#USAGE_VOICE_COMMUNICATION" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING" in android.media.AudioAttributes.Builder [101] -android/media/AudioFormat.java:963: lint: Unresolved link/see tag "android.media.AudioSystem#OUT_CHANNEL_COUNT_MAX AudioSystem#OUT_CHANNEL_COUNT_MAX" in android.media.AudioFormat.Builder [101] -android/media/AudioManager.java:275: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] -android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] -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] -android/media/tv/TableResponse.java:82: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableResponse [101] -android/net/EthernetNetworkSpecifier.java:21: lint: Unresolved link/see tag "android.net.EthernetManager" in android.net.EthernetNetworkSpecifier [101] -android/net/eap/EapSessionConfig.java:120: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:135: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:148: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:161: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaConfig [101] -android/net/eap/EapSessionConfig.java:390: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaPrimeConfig [101] -android/net/eap/EapSessionConfig.java:587: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapSimConfig [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_6_GHZ WifiScanner#WIFI_BAND_6_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_UNSPECIFIED WifiScanner#WIFI_BAND_UNSPECIFIED" in android.net.wifi.MloLink [101] -android/net/wifi/SoftApConfiguration.java:9: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder SoftApConfiguration.Builder" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:66: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setSsid(java.lang.String) Builder#setSsid(String)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:85: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setWifiSsid(android.net.wifi.WifiSsid) Builder#setWifiSsid(WifiSsid)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:96: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBssid(android.net.MacAddress) Builder#setBssid(MacAddress)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:107: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setPassphrase(java.lang.String,int) Builder#setPassphrase(String, int)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:118: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setHiddenSsid(boolean) Builder#setHiddenSsid(boolean)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:2466: lint: Unresolved link/see tag "TelephonyManager#hasCarrierPrivileges()." in android.net.wifi.WifiManager [101] -android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig [101] -android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig [101] -android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101] -android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101] -android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig [101] -android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101] -android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101] -android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101] -android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101] -android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101] -android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101] -android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101] -android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101] -android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101] -android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101] -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] -android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] -android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101] -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] -android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101] -android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] +// These are javadoc errors for @ChangeId constants, which are problematic to generate documentation +// for. They're not necessarily errors in the docs themselves but are also a limitation in the tool. +// Regardless, the docs currently generated for them is not good, but it is also not used directly +// in production at the moment. +// The main limitation is that all references must be fully qualified in order to resolve properly +// (aside from the normal limitatinos of only being able to @link public APIs). +// See the CompatInfo.java source file in doclava for more information. +android/net/wifi/SoftApConfiguration.java:171: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101] -android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101] -android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android.service.voice.AlwaysOnHotwordDetector [101] -android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] +android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] +android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onFailure" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onUnknownFailure" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector#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] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] +com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" 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/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.IdentifierType#DAB_SID_EXT" in android [101] +com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101] +com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.RadioTuner" in android [101] +com/android/server/devicepolicy/DevicePolicyManagerService.java:861: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" 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] com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34" in android [101] com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101] -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] +com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] +com/android/server/pm/PackageInstallerSession.java:330: lint: Unresolved link/see tag "com.android.android.server.pm#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] +com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
\ No newline at end of file diff --git a/core/api/current.txt b/core/api/current.txt index f908d9546a34..66aeb0f7cbaf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android { public final class Manifest { @@ -3288,10 +3286,10 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); - method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); - method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -3403,9 +3401,9 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9 field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 - field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 - field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 - field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final String SERVICE_META_DATA = "android.accessibilityservice"; field public static final int SHOW_MODE_AUTO = 0; // 0x0 @@ -5741,6 +5739,7 @@ package android.app { ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet); ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int); method @Deprecated public void onBackStackChanged(); + method @Deprecated protected void onLayout(boolean, int, int, int, int); method @Deprecated public void setActivity(android.app.Activity); method @Deprecated public void setMaxVisible(int); method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener); @@ -9789,7 +9788,7 @@ package android.content { method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); - method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); + method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); method @NonNull public android.content.AttributionSource.Builder setPid(int); } @@ -10572,7 +10571,7 @@ package android.content { public final class ContextParams { method @Nullable public String getAttributionTag(); method @Nullable public android.content.AttributionSource getNextAttributionSource(); - method @NonNull public boolean shouldRegisterAttributionSource(); + method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource(); } public static final class ContextParams.Builder { @@ -10581,7 +10580,7 @@ package android.content { method @NonNull public android.content.ContextParams build(); method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String); method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource); - method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); + method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); } public class ContextWrapper extends android.content.Context { @@ -13653,6 +13652,7 @@ package android.content.res { public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable { method public void close(); + method public String getAttributeNamespace(int); } } @@ -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(); @@ -20405,7 +20405,7 @@ package android.inputmethodservice { method @Deprecated public boolean isPreviewEnabled(); method @Deprecated public boolean isProximityCorrectionEnabled(); method @Deprecated public boolean isShifted(); - method public void onClick(android.view.View); + method @Deprecated public void onClick(android.view.View); method @Deprecated public void onDetachedFromWindow(); method @Deprecated public void onDraw(android.graphics.Canvas); method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key); @@ -24232,7 +24232,7 @@ package android.media { @Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor { method @Deprecated public void apply(); - method public Object clone() throws java.lang.CloneNotSupportedException; + method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException; method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException; method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException; method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException; @@ -25787,15 +25787,15 @@ package android.media.midi { method public abstract void onDisconnect(android.media.midi.MidiReceiver); } - public abstract class MidiUmpDeviceService extends android.app.Service { + @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service { ctor public MidiUmpDeviceService(); - method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); - method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); - method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); - method public void onClose(); - method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); - method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); - field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); + field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; } } @@ -27096,6 +27096,7 @@ package android.media.tv { method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations(); method public String getSelectedTrack(int); method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int); + method protected void onLayout(boolean, int, int, int, int); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource); method public void reset(); @@ -33094,6 +33095,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 +33673,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[]); @@ -43531,7 +43550,6 @@ package android.telephony { method public int getLongitude(); method public int getNetworkId(); method public int getSystemId(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; } @@ -43547,7 +43565,6 @@ package android.telephony { method @Nullable public String getMncString(); method @Nullable public String getMobileNetworkOperator(); method @Deprecated public int getPsc(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR; } @@ -43565,7 +43582,6 @@ package android.telephony { method @Nullable public String getMobileNetworkOperator(); method public int getPci(); method public int getTac(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR; } @@ -43578,7 +43594,6 @@ package android.telephony { method @IntRange(from=0, to=3279165) public int getNrarfcn(); method @IntRange(from=0, to=1007) public int getPci(); method @IntRange(from=0, to=16777215) public int getTac(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR; } @@ -43592,7 +43607,6 @@ package android.telephony { method @Nullable public String getMncString(); method @Nullable public String getMobileNetworkOperator(); method public int getUarfcn(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR; } @@ -43608,7 +43622,6 @@ package android.telephony { method @Nullable public String getMobileNetworkOperator(); method public int getPsc(); method public int getUarfcn(); - method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR; } @@ -43692,6 +43705,7 @@ package android.telephony { public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method public int getCdmaDbm(); method public int getCdmaEcio(); @@ -43702,24 +43716,28 @@ package android.telephony { method public int getEvdoLevel(); method public int getEvdoSnr(); method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR; } public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method public int getBitErrorRate(); method public int getDbm(); method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel(); method public int getRssi(); method public int getTimingAdvance(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR; } public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method @IntRange(from=0, to=15) public int getCqi(); method @IntRange(from=1, to=6) public int getCqiTableIndex(); @@ -43730,12 +43748,14 @@ package android.telephony { method public int getRssi(); method public int getRssnr(); method public int getTimingAdvance(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR; } public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport(); method @IntRange(from=1, to=3) public int getCsiCqiTableIndex(); @@ -43748,26 +43768,31 @@ package android.telephony { method public int getSsRsrq(); method public int getSsSinr(); method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR; } public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method public int getDbm(); method @IntRange(from=0, to=4) public int getLevel(); method public int getRscp(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR; } public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); + method public boolean equals(Object); method public int getAsuLevel(); method public int getDbm(); method public int getEcNo(); method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel(); + method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR; } @@ -46555,6 +46580,7 @@ package android.text { method @Deprecated public int length(); method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int); method @Deprecated public CharSequence subSequence(int, int); + method @Deprecated public String toString(); } @Deprecated public class AndroidCharacter { @@ -47006,6 +47032,7 @@ package android.text { method public void removeSpan(Object); method public void setSpan(Object, int, int, int); method public CharSequence subSequence(int, int); + method public String toString(); } public static final class PrecomputedText.Params { @@ -47135,6 +47162,7 @@ package android.text { method public void setFilters(android.text.InputFilter[]); method public void setSpan(Object, int, int, int); method public CharSequence subSequence(int, int); + method public String toString(); method public static android.text.SpannableStringBuilder valueOf(CharSequence); } @@ -48775,7 +48803,9 @@ package android.util { method public boolean containsValue(Object); method public void ensureCapacity(int); method public java.util.Set<java.util.Map.Entry<K,V>> entrySet(); + method public boolean equals(@Nullable Object); method public V get(Object); + method public int hashCode(); method public int indexOfKey(Object); method public int indexOfValue(Object); method public boolean isEmpty(); @@ -48807,7 +48837,9 @@ package android.util { method public boolean contains(Object); method public boolean containsAll(java.util.Collection<?>); method public void ensureCapacity(int); + method public boolean equals(@Nullable Object); method public void forEach(java.util.function.Consumer<? super E>); + method public int hashCode(); method public int indexOf(Object); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); @@ -53129,6 +53161,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 +53231,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 +53581,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 +53591,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); @@ -57542,6 +57578,7 @@ package android.widget { ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet); ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int); ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int); + method @Deprecated protected void onLayout(boolean, int, int, int, int); } @Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams { @@ -57620,6 +57657,7 @@ package android.widget { method public long getSelectedItemId(); method public int getSelectedItemPosition(); method public abstract android.view.View getSelectedView(); + method protected void onLayout(boolean, int, int, int, int); method public boolean performItemClick(android.view.View, int, long); method public abstract void setAdapter(T); method public void setEmptyView(android.view.View); @@ -58266,6 +58304,7 @@ package android.widget { method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet); method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring(); method public boolean getMeasureAllChildren(); + method protected void onLayout(boolean, int, int, int, int); method public void setMeasureAllChildren(boolean); } @@ -58319,6 +58358,7 @@ package android.widget { method public boolean getUseDefaultMargins(); method public boolean isColumnOrderPreserved(); method public boolean isRowOrderPreserved(); + method protected void onLayout(boolean, int, int, int, int); method public void setAlignmentMode(int); method public void setColumnCount(int); method public void setColumnOrderPreserved(boolean); @@ -58541,6 +58581,7 @@ package android.widget { method public float getWeightSum(); method public boolean isBaselineAligned(); method public boolean isMeasureWithLargestChildEnabled(); + method protected void onLayout(boolean, int, int, int, int); method public void setBaselineAligned(boolean); method public void setBaselineAlignedChildIndex(int); method public void setDividerDrawable(android.graphics.drawable.Drawable); @@ -59089,6 +59130,7 @@ package android.widget { method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet); method public int getGravity(); method public int getIgnoreGravity(); + method protected void onLayout(boolean, int, int, int, int); method public void setGravity(int); method public void setHorizontalGravity(int); method public void setIgnoreGravity(int); @@ -59543,6 +59585,7 @@ package android.widget { method @Deprecated public boolean isMoving(); method @Deprecated public boolean isOpened(); method @Deprecated public void lock(); + method @Deprecated protected void onLayout(boolean, int, int, int, int); method @Deprecated public void open(); method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener); method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener); @@ -60189,6 +60232,7 @@ package android.widget { method public boolean hideOverflowMenu(); method public void inflateMenu(@MenuRes int); method public boolean isOverflowMenuShowing(); + method protected void onLayout(boolean, int, int, int, int); method public void setCollapseContentDescription(@StringRes int); method public void setCollapseContentDescription(@Nullable CharSequence); method public void setCollapseIcon(@DrawableRes int); @@ -60343,7 +60387,7 @@ package android.widget { method @Deprecated public android.view.View getZoomControls(); method @Deprecated public boolean isAutoDismissed(); method @Deprecated public boolean isVisible(); - method public boolean onTouch(android.view.View, android.view.MotionEvent); + method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent); method @Deprecated public void setAutoDismissed(boolean); method @Deprecated public void setFocusable(boolean); method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 81579a214bc9..b5d3ed7c8a7e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -8,9 +6,7 @@ package android { field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; } } diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/core/api/module-lib-removed.txt +++ b/core/api/module-lib-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/core/api/removed.txt b/core/api/removed.txt index e2b4e4dfc6c4..5a4be65ef559 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.app { public class Notification implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c001e0ea2125..5d1f6dc3e675 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -299,6 +297,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"; @@ -313,7 +312,7 @@ package android { field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; - field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS"; + field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS"; field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; @@ -655,7 +654,6 @@ package android.app { field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; - field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; @@ -3557,7 +3555,7 @@ package android.content { field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; - field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; + field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -4030,7 +4028,7 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L + field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 @@ -4061,7 +4059,9 @@ package android.content.pm { } public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable { + method public int describeContents(); method public void onUninstallComplete(@NonNull String, int, @Nullable String); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR; } @@ -5961,6 +5961,7 @@ package android.hardware.soundtrigger { public static final class SoundTrigger.Keyphrase implements android.os.Parcelable { ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]); + method public int describeContents(); method public int getId(); method @NonNull public java.util.Locale getLocale(); method public int getRecognitionModes(); @@ -5983,6 +5984,7 @@ package android.hardware.soundtrigger { public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable { ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int); ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]); + method public int describeContents(); method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases(); method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -5990,6 +5992,7 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { + method public int describeContents(); method public int getEnd(); method public int getStart(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -6717,7 +6720,7 @@ package android.media.audiopolicy { method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public String toLogFriendlyString(); - method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>); + method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>); field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1 @@ -6800,6 +6803,7 @@ package android.media.musicrecognition { public abstract class MusicRecognitionService extends android.app.Service { ctor public MusicRecognitionService(); + method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback); } @@ -6858,6 +6862,7 @@ package android.media.soundtrigger { public abstract class SoundTriggerDetectionService extends android.app.Service { ctor public SoundTriggerDetectionService(); + method public final android.os.IBinder onBind(android.content.Intent); method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle); method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle); method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int); @@ -7571,6 +7576,7 @@ package android.media.tv.tuner.filter { } public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent { + method protected void finalize(); method public long getAudioHandle(); method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations(); method public long getAvDataId(); @@ -8965,6 +8971,8 @@ package android.net { package android.net.metrics { @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); } @Deprecated public static final class ApfProgramEvent.Builder { @@ -8979,6 +8987,8 @@ package android.net.metrics { } @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event { + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); } @Deprecated public static final class ApfStats.Builder { @@ -8997,6 +9007,8 @@ package android.net.metrics { } @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event { + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); } @Deprecated public static final class DhcpClientEvent.Builder { @@ -9008,7 +9020,9 @@ package android.net.metrics { @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event { ctor @Deprecated public DhcpErrorEvent(int); + method @Deprecated public int describeContents(); method @Deprecated public static int errorCodeWithOption(int, int); + method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000 field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000 field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000 @@ -9046,6 +9060,8 @@ package android.net.metrics { @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event { ctor @Deprecated public IpManagerEvent(int, long); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3 field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8 field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7 @@ -9058,6 +9074,8 @@ package android.net.metrics { @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event { ctor @Deprecated public IpReachabilityEvent(int); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated public static final int NUD_FAILED = 512; // 0x200 field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400 field @Deprecated public static final int PROBE = 256; // 0x100 @@ -9068,6 +9086,8 @@ package android.net.metrics { @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event { ctor @Deprecated public NetworkEvent(int, long); ctor @Deprecated public NetworkEvent(int); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4 field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1 field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc @@ -9084,6 +9104,8 @@ package android.net.metrics { } @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); } @Deprecated public static final class RaEvent.Builder { @@ -9098,7 +9120,9 @@ package android.net.metrics { } @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { + method @Deprecated public int describeContents(); method @Deprecated @NonNull public static String getProbeName(int); + method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated public static final int DNS_FAILURE = 0; // 0x0 field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1 field @Deprecated public static final int PROBE_DNS = 0; // 0x0 @@ -11503,6 +11527,7 @@ package android.service.assist.classification { public abstract class FieldClassificationService extends android.app.Service { ctor public FieldClassificationService(); + method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>); method public void onConnected(); method public void onDisconnected(); @@ -11581,6 +11606,7 @@ package android.service.autofill.augmented { method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]); method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory(); + method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback); @@ -11619,6 +11645,7 @@ package android.service.autofill.augmented { public final class FillWindow implements java.lang.AutoCloseable { ctor public FillWindow(); + method public void close(); method public void destroy(); method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long); } @@ -11644,6 +11671,7 @@ package android.service.carrier { public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable { ctor public CarrierMessagingServiceWrapper(); method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable); + method public void close(); method public void disconnect(); method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback); method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback); @@ -11694,6 +11722,7 @@ package android.service.contentcapture { method public final void disableSelf(); method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent); method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData); + method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent); method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId); @@ -11732,6 +11761,7 @@ package android.service.contentsuggestions { public abstract class ContentSuggestionsService extends android.app.Service { ctor public ContentSuggestionsService(); + method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback); method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle); method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle); @@ -11745,6 +11775,7 @@ package android.service.dataloader { public abstract class DataLoaderService extends android.app.Service { ctor public DataLoaderService(); + method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams); } @@ -12417,6 +12448,7 @@ package android.service.tracing { public class TraceReportService extends android.app.Service { ctor public TraceReportService(); + method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams); } @@ -13042,6 +13074,7 @@ package android.telecom { method @Nullable public android.content.ComponentName getCallScreeningComponent(); method public boolean isBlocked(); method public boolean isInContacts(); + method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR; } @@ -13351,6 +13384,7 @@ package android.telephony { method public int getReason(); method public int getTimeoutSeconds(); method public boolean isEnabled(); + method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR; field public static final int REASON_ALL = 4; // 0x4 field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5 @@ -13635,6 +13669,7 @@ package android.telephony { method public int getDownlinkCapacityKbps(); method public int getType(); method public int getUplinkCapacityKbps(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR; field public static final int INVALID = -1; // 0xffffffff field public static final int LCE_TYPE_COMBINED = 2; // 0x2 @@ -13644,8 +13679,10 @@ package android.telephony { public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo { ctor public LteVopsSupportInfo(int, int); + method public boolean equals(@Nullable Object); method public int getEmcBearerSupport(); method public int getVopsSupport(); + method public int hashCode(); method public boolean isEmergencyServiceFallbackSupported(); method public boolean isEmergencyServiceSupported(); method public boolean isVopsSupported(); @@ -13746,9 +13783,11 @@ package android.telephony { public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo { ctor public NrVopsSupportInfo(int, int, int); + method public boolean equals(@Nullable Object); method public int getEmcSupport(); method public int getEmfSupport(); method public int getVopsSupport(); + method public int hashCode(); method public boolean isEmergencyServiceFallbackSupported(); method public boolean isEmergencyServiceSupported(); method public boolean isVopsSupported(); @@ -14861,6 +14900,7 @@ package android.telephony.data { public abstract class QualifiedNetworksService extends android.app.Service { ctor public QualifiedNetworksService(); + method public android.os.IBinder onBind(android.content.Intent); method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int); field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService"; } @@ -15052,6 +15092,7 @@ package android.telephony.gba { public class GbaService extends android.app.Service { ctor public GbaService(); method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean); + method public android.os.IBinder onBind(android.content.Intent); method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException; method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException; field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService"; @@ -15554,6 +15595,7 @@ package android.telephony.ims { method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int); method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int); + method public android.os.IBinder onBind(android.content.Intent); method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException; method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures(); method public void readyForFeatureCreation(); @@ -16699,6 +16741,23 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; } + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR; + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1 + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength); + } + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees(); @@ -16734,6 +16793,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); @@ -16744,6 +16804,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); @@ -16752,6 +16813,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 1fa2718dc6d2..aa17df3471d7 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.app { public class AppOpsManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3716546b88bd..384b9573528e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -30,7 +28,6 @@ package android { field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE"; @@ -58,7 +55,6 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; @@ -842,7 +838,7 @@ package android.appwidget { package android.companion { - public static final class AssociationInfo.Builder { + @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder { ctor public AssociationInfo.Builder(int, int, @NonNull String); ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo); method @NonNull public android.companion.AssociationInfo build(); @@ -1183,6 +1179,7 @@ package android.credentials { method @Nullable public CharSequence getLabel(@NonNull android.content.Context); method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity(); method @Nullable public CharSequence getSettingsSubtitle(); method @NonNull public boolean hasCapability(@NonNull String); method public boolean isEnabled(); @@ -3188,7 +3185,6 @@ package android.telephony { field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2 field public static final int HAL_SERVICE_MODEM = 3; // 0x3 field public static final int HAL_SERVICE_NETWORK = 4; // 0x4 - field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 field public static final int HAL_SERVICE_SIM = 5; // 0x5 field public static final int HAL_SERVICE_VOICE = 6; // 0x6 field public static final android.util.Pair HAL_VERSION_UNKNOWN; @@ -3588,8 +3584,8 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000 field public CharSequence accessibilityTitle; - field public float preferredMaxDisplayRefreshRate; - field public float preferredMinDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate; field public int privateFlags; } @@ -3619,7 +3615,6 @@ package android.view.accessibility { public final class AccessibilityWindowInfo implements android.os.Parcelable { method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); - field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff } } @@ -3802,14 +3797,14 @@ package android.view.inputmethod { public final class InputMethodManager { method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession(); method public int getDisplayId(); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int); method public boolean hasActiveInputConnection(@Nullable android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests(); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown(); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long); field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L } @@ -3947,7 +3942,9 @@ package android.widget.inline { package android.window { public final class BackNavigationInfo implements android.os.Parcelable { + method public int describeContents(); method @NonNull public static String typeToString(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR; field public static final String KEY_TRIGGER_BACK = "TriggerBack"; field public static final int TYPE_CALLBACK = 4; // 0x4 @@ -4008,11 +4005,13 @@ package android.window { } public final class TaskFragmentCreationParams implements android.os.Parcelable { + method public int describeContents(); method @NonNull public android.os.IBinder getFragmentToken(); method @NonNull public android.graphics.Rect getInitialRelativeBounds(); method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer(); method @NonNull public android.os.IBinder getOwnerToken(); method public int getWindowingMode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR; } @@ -4024,6 +4023,7 @@ package android.window { } public final class TaskFragmentInfo implements android.os.Parcelable { + method public int describeContents(); method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo); method @NonNull public java.util.List<android.os.IBinder> getActivities(); method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment(); @@ -4037,6 +4037,7 @@ package android.window { method public boolean isEmpty(); method public boolean isTaskClearedForReuse(); method public boolean isVisible(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR; } @@ -4059,6 +4060,8 @@ package android.window { } public final class TaskFragmentOrganizerToken implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR; } diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3370c121acfe..1000612ee0e2 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -23,6 +23,7 @@ import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.CheckResult; import android.annotation.ColorInt; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -793,6 +794,7 @@ public abstract class AccessibilityService extends Service { * @hide */ @Retention(RetentionPolicy.SOURCE) + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") @IntDef( prefix = {"OVERLAY_RESULT_"}, value = { @@ -803,6 +805,7 @@ public abstract class AccessibilityService extends Service { public @interface AttachOverlayResult {} /** Result code indicating the overlay was successfully attached. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; /** @@ -810,6 +813,7 @@ public abstract class AccessibilityService extends Service { * error and not * because of problems with the input. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; /** @@ -817,6 +821,7 @@ public abstract class AccessibilityService extends Service { * specified display or * window id was invalid. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; private int mConnectionId = AccessibilityInteractionClient.NO_ID; @@ -3506,11 +3511,7 @@ public abstract class AccessibilityService extends Service { * @param displayId the display to which the SurfaceControl should be attached. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); AccessibilityInteractionClient.getInstance(this) @@ -3547,6 +3548,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay( int displayId, @NonNull SurfaceControl sc, @@ -3581,11 +3583,7 @@ public abstract class AccessibilityService extends Service { * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); @@ -3623,6 +3621,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc, diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 895dde760058..f2c00517ad16 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -2073,9 +2073,7 @@ public class ActivityOptions extends ComponentOptions { * Allow a {@link PendingIntent} to use the privilege of its creator to start background * activities. * - * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set - * @throws IllegalArgumentException is the value is not a valid - * {@link android.app.ComponentOptions.BackgroundActivityStartMode} + * @param mode the mode being set */ @NonNull public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode( @@ -2088,7 +2086,7 @@ public class ActivityOptions extends ComponentOptions { * Returns the mode to start background activities granted by the creator of the * {@link PendingIntent}. * - * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set + * @return the mode currently set */ public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() { return mPendingIntentCreatorBackgroundActivityStartMode; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 68589456dec3..ecbc9b1d52c2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1487,13 +1487,13 @@ public class AppOpsManager { AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; /** - * Allows the assistant app to get the training data from the trusted process to improve the - * hotword training model. + * Allows the privileged assistant app to receive the training data from the sandboxed hotword + * detection service. * * @hide */ - public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA = - AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA; + public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = + AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1641,7 +1641,7 @@ public class AppOpsManager { OPSTR_CAMERA_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, - OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA + OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA }) public @interface AppOpString {} @@ -2258,18 +2258,17 @@ public class AppOpsManager { * * @hide */ - @SystemApi public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "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; @@ -2382,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[]{ @@ -2815,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/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 9549ebf698a8..41b400459526 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -843,8 +843,7 @@ public class BroadcastOptions extends ComponentOptions { * considered to be in the same delivery group as this iff it has the same {@code namespace} * and {@code key}. * - * <p> If neither matching key using this API nor matching filter using - * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default + * <p> If not matching key using this API then by default * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group. */ @NonNull diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 8fea03ba47f5..ebf183ff18c3 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -50,7 +50,6 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.security.net.config.NetworkSecurityConfigProvider; -import android.sysprop.VndkProperties; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; @@ -901,14 +900,10 @@ public final class LoadedApk { } // Similar to vendor apks, we should add /product/lib for apks from product partition - // when product apps are marked as unbundled. We cannot use the same way from vendor - // to check if lib path exists because there is possibility that /product/lib would not - // exist from legacy device while product apks are bundled. To make this clear, we use - // "ro.product.vndk.version" property. If the property is defined, we regard all product - // apks as unbundled. + // when product apps are marked as unbundled. Product is separated as long as the + // partition exists, so it can be handled with same approach from the vendor partition. if (mApplicationInfo.getCodePath() != null - && mApplicationInfo.isProduct() - && VndkProperties.product_vndk_version().isPresent()) { + && mApplicationInfo.isProduct()) { isBundledApp = false; } diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 0857c9655e8d..1fdc51687433 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -133,15 +132,14 @@ public class LocaleConfig implements Parcelable { return; } } - int resId = 0; Resources res = context.getResources(); + //Get the resource id + int resId = context.getApplicationInfo().getLocaleConfigRes(); + if (resId == 0) { + mStatus = STATUS_NOT_SPECIFIED; + return; + } try { - //Get the resource id - resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes(); - if (resId == 0) { - mStatus = STATUS_NOT_SPECIFIED; - return; - } //Get the parser to read XML data XmlResourceParser parser = res.getXml(resId); parseLocaleConfig(parser, res); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dd7db23d668b..94e1292a7554 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1514,13 +1514,13 @@ public class Notification implements Parcelable /** * {@link #extras} key: the color used as a hint for the Answer action button of a - * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. + * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}. */ public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; /** * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a - * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. + * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}. */ public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; @@ -1704,7 +1704,7 @@ public class Notification implements Parcelable private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; /** - * {@link }: No semantic action defined. + * No semantic action defined. */ public static final int SEMANTIC_ACTION_NONE = 0; @@ -7923,7 +7923,7 @@ public class Notification implements Parcelable * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated * conversation section in the shade above non-conversation alerting and silence notifications. * To be a valid conversation shortcut, the shortcut must be a - * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut. + * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut. * * <p> * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 15bd1dcc3980..e2689687f388 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -651,6 +651,7 @@ public class AssistStructure implements Parcelable { // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. AutofillOverlay mAutofillOverlay; + boolean mIsCredential; int mX; int mY; @@ -799,6 +800,7 @@ public class AssistStructure implements Parcelable { if (autofillFlags != 0) { mSanitized = in.readInt() == 1; + mIsCredential = in.readInt() == 1; mImportantForAutofill = in.readInt(); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1033,6 +1035,7 @@ public class AssistStructure implements Parcelable { if (autofillFlags != 0) { out.writeInt(mSanitized ? 1 : 0); + out.writeInt(mIsCredential ? 1 : 0); out.writeInt(mImportantForAutofill); writeSensitive = mSanitized || !sanitizeOnWrite; if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1246,6 +1249,19 @@ public class AssistStructure implements Parcelable { } /** + * @return whether the node is a credential. + * + * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, + * not for assist purposes. + * TODO(b/303677885): add TestApi + * + * @hide + */ + public boolean isCredential() { + return mIsCredential; + } + + /** * Gets the {@link android.text.InputType} bits of this structure. * * @return bits as defined by {@link android.text.InputType}. @@ -2183,6 +2199,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setIsCredential(boolean isCredential) { + mNode.mIsCredential = isCredential; + } + + @Override public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) { mNode.mReceiveContentMimeTypes = mimeTypes; } @@ -2498,7 +2519,9 @@ public class AssistStructure implements Parcelable { + ", value=" + node.getAutofillValue() + ", sanitized=" + node.isSanitized() + ", important=" + node.getImportantForAutofill() - + ", visibility=" + node.getVisibility()); + + ", visibility=" + node.getVisibility() + + ", isCredential=" + node.isCredential() + ); } final int NCHILDREN = node.getChildCount(); diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java new file mode 100644 index 000000000000..98281338872b --- /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/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index afe87de1dbf5..f401a7607364 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -2,8 +2,14 @@ package: "android.app.usage" flag { name: "user_interaction_type_api" - namespace: "power_optimization" + namespace: "backstage_power" description: "Feature flag for user interaction event report/query API" bug: "296061232" } +flag { + name: "report_usage_stats_permission" + namespace: "backstage_power" + description: "Feature flag for the new REPORT_USAGE_STATS permission." + bug: "296056771" +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 083fa0041b26..6393c456bdcd 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -15,6 +15,7 @@ */ package android.companion; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -412,6 +413,7 @@ public final class AssociationInfo implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) @TestApi public static final class Builder { private final int mId; diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 03e75e9fc483..570ecaa47b4e 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/flags.aconfig b/core/java/android/companion/flags.aconfig new file mode 100644 index 000000000000..b9e5609171c3 --- /dev/null +++ b/core/java/android/companion/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.companion" + +flag { + name: "new_association_builder" + namespace: "companion" + description: "Controls if the new Builder is exposed to test apis." + bug: "296251481" +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 0af4c92e0b63..93a3e7822888 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 bf78dd09e7c2..b9451a74f9e9 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/AttributionSource.java b/core/java/android/content/AttributionSource.java index bfc1eec829e8..62fbcaff79e3 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -730,7 +730,7 @@ public final class AttributionSource implements Parcelable { /** * The next app to receive the permission protected data. * - * @deprecated Use {@link setNextAttributionSource} instead. + * @deprecated Use {@link #setNextAttributionSource} instead. */ @Deprecated public @NonNull Builder setNext(@Nullable AttributionSource value) { @@ -744,6 +744,7 @@ public final class AttributionSource implements Parcelable { /** * The next app to receive the permission protected data. */ + @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE) public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 36e0529e3566..3fbcd704308b 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -30,6 +30,11 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Content capture options for a given package. @@ -119,7 +124,10 @@ public final class ContentCaptureOptions implements Parcelable { /* enableReceiver= */ false, new ContentProtectionOptions( /* enableReceiver= */ false, - /* bufferSize= */ 0), + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0), /* whitelistedComponents= */ null); } @@ -141,9 +149,7 @@ public final class ContentCaptureOptions implements Parcelable { logHistorySize, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -183,9 +189,7 @@ public final class ContentCaptureOptions implements Parcelable { ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -386,9 +390,58 @@ public final class ContentCaptureOptions implements Parcelable { */ public final int bufferSize; - public ContentProtectionOptions(boolean enableReceiver, int bufferSize) { + /** + * The list of required groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> requiredGroups; + + /** + * The list of optional groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> optionalGroups; + + /** + * The minimal number of optional groups that have to be matched. This is the threshold + * value and comparison is done with greater than or equals. + * + * @hide + */ + public final int optionalGroupsThreshold; + + /** + * Empty constructor with default values. + * + * @hide + */ + public ContentProtectionOptions() { + this( + ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); + } + + /** + * Full primary constructor. + * + * @hide + */ + public ContentProtectionOptions( + boolean enableReceiver, + int bufferSize, + @NonNull List<List<String>> requiredGroups, + @NonNull List<List<String>> optionalGroups, + int optionalGroupsThreshold) { this.enableReceiver = enableReceiver; this.bufferSize = bufferSize; + this.requiredGroups = requiredGroups; + this.optionalGroups = optionalGroups; + this.optionalGroupsThreshold = optionalGroupsThreshold; } @Override @@ -398,7 +451,14 @@ public final class ContentCaptureOptions implements Parcelable { .append("enableReceiver=") .append(enableReceiver) .append(", bufferSize=") - .append(bufferSize); + .append(bufferSize) + .append(", requiredGroupsSize=") + .append(requiredGroups.size()) + .append(", optionalGroupsSize=") + .append(optionalGroups.size()) + .append(", optionalGroupsThreshold=") + .append(optionalGroupsThreshold); + return stringBuilder.append(']').toString(); } @@ -407,17 +467,50 @@ public final class ContentCaptureOptions implements Parcelable { pw.print(enableReceiver); pw.print(", bufferSize="); pw.print(bufferSize); + pw.print(", requiredGroupsSize="); + pw.print(requiredGroups.size()); + pw.print(", optionalGroupsSize="); + pw.print(optionalGroups.size()); + pw.print(", optionalGroupsThreshold="); + pw.print(optionalGroupsThreshold); } - private void writeToParcel(Parcel parcel) { + private void writeToParcel(@NonNull Parcel parcel) { parcel.writeBoolean(enableReceiver); parcel.writeInt(bufferSize); + writeGroupsToParcel(requiredGroups, parcel); + writeGroupsToParcel(optionalGroups, parcel); + parcel.writeInt(optionalGroupsThreshold); } - private static ContentProtectionOptions createFromParcel(Parcel parcel) { + @NonNull + private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) { boolean enableReceiver = parcel.readBoolean(); int bufferSize = parcel.readInt(); - return new ContentProtectionOptions(enableReceiver, bufferSize); + List<List<String>> requiredGroups = createGroupsFromParcel(parcel); + List<List<String>> optionalGroups = createGroupsFromParcel(parcel); + int optionalGroupsThreshold = parcel.readInt(); + return new ContentProtectionOptions( + enableReceiver, + bufferSize, + requiredGroups, + optionalGroups, + optionalGroupsThreshold); + } + + private static void writeGroupsToParcel( + @NonNull List<List<String>> groups, @NonNull Parcel parcel) { + parcel.writeInt(groups.size()); + groups.forEach(parcel::writeStringList); + } + + @NonNull + private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) { + int size = parcel.readInt(); + return IntStream.range(0, size) + .mapToObj(i -> new ArrayList<String>()) + .peek(parcel::readStringList) + .collect(Collectors.toUnmodifiableList()); } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b6a98a5b8f83..59bb73b5916d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -323,7 +323,7 @@ public abstract class Context { // Make sure no flag uses the sign bit (most significant bit) of the long integer, // to avoid future confusion. BIND_BYPASS_USER_NETWORK_RESTRICTIONS, - BIND_FILTER_OUT_QUARANTINED_COMPONENTS, + BIND_MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface BindServiceFlagsLongBits {} @@ -703,7 +703,7 @@ public abstract class Context { * * @hide */ - public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L; + public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L; /** @@ -4357,7 +4357,6 @@ public abstract class Context { * @see android.telephony.CarrierConfigManager * @see #EUICC_SERVICE * @see android.telephony.euicc.EuiccManager - * @see android.telephony.MmsManager * @see #INPUT_METHOD_SERVICE * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index 988a9c0ab9b3..b844d358a523 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -16,7 +16,10 @@ package android.content; +import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE; + import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -102,6 +105,7 @@ public final class ContextParams { * registered. */ @NonNull + @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) public boolean shouldRegisterAttributionSource() { return mShouldRegisterAttributionSource; } @@ -179,6 +183,7 @@ public final class ContextParams { * created should be registered. */ @NonNull + @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) public Builder setShouldRegisterAttributionSource(boolean shouldRegister) { mShouldRegisterAttributionSource = shouldRegister; return this; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5f4c05f001ff..b765562ab587 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3929,6 +3929,8 @@ public class Intent implements Parcelable, Cloneable { * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground * broadcast, since it is part of a visible user interaction; be as quick * as possible when handling it. + * + * <p><b>Note:</b> This broadcast is not sent to the system user. */ public static final String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE"; @@ -4180,7 +4182,7 @@ public class Intent implements Parcelable, Cloneable { * new state of quiet mode. This is only sent to registered receivers, not manifest receivers. * * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a - * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. + * generic broadcast for all profile users. */ @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE) public static final String ACTION_PROFILE_AVAILABLE = @@ -4194,7 +4196,7 @@ public class Intent implements Parcelable, Cloneable { * new state of quiet mode. This is only sent to registered receivers, not manifest receivers. * * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as - * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. + * a generic broadcast for all profile users. */ @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE) public static final String ACTION_PROFILE_UNAVAILABLE = @@ -4222,7 +4224,7 @@ public class Intent implements Parcelable, Cloneable { * that was removed. * * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a - * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. + * generic broadcast for all profile users. * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a * managed user is removed. * @@ -4242,7 +4244,7 @@ public class Intent implements Parcelable, Cloneable { * that was added. * * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a - * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. + * generic broadcast for all profile users. * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a * managed user is added. * @@ -5323,6 +5325,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; // --------------------------------------------------------------------- diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java index f256372964f2..30875aa6a100 100644 --- a/core/java/android/content/om/OverlayIdentifier.java +++ b/core/java/android/content/om/OverlayIdentifier.java @@ -39,7 +39,6 @@ import java.util.Objects; * --> * * @see OverlayInfo#getOverlayIdentifier() - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false, genEqualsHashCode = true, genToString = false) diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index ff1c08801dd7..2e898562655b 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -385,7 +385,6 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * <p>The return value of this function can be used to unregister the related overlay. * * @return an identifier representing the current overlay. - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @Override @NonNull diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 0fcc72a1f688..ed965b3d1777 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -50,7 +50,7 @@ import java.util.List; * <li>register overlays * <li>unregister overlays * <li>execute multiple operations in one commitment by calling {@link - * OverlayManagerTransaction#commit()} + * #commit(OverlayManagerTransaction)} * </ul> * * @see OverlayManagerTransaction diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 036a4eb22ba6..aefa55f30826 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1185,8 +1185,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly * through - * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame, - * {@link android.view.ViewRootImpl}#getDisplayFrame respectively. + * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, + * {@code android.view.ViewRootImpl#getDisplayFrame} respectively. * * <p>Some applications assume that they occupy the whole screen and therefore use the display * coordinates in their calculations as if an activity is positioned in the top-left corner of diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl index 7ab7ed1cc5df..74953fff40d8 100644 --- a/core/java/android/content/pm/ArchivedActivityParcel.aidl +++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl @@ -16,9 +16,12 @@ package android.content.pm; +import android.content.ComponentName; + /** @hide */ parcelable ArchivedActivityParcel { String title; + ComponentName originalComponentName; // PNG compressed bitmaps. byte[] iconBitmap; byte[] monochromeIconBitmap; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 673a8a5edcba..d837aae35096 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -371,6 +371,13 @@ public class PackageInstaller { "android.content.pm.extra.UNARCHIVE_ALL_USERS"; /** + * A list of warnings that occurred during installation. + * + * @hide + */ + public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; + + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * @@ -2723,8 +2730,8 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS - * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} + * permission to be held by the caller. Revoking runtime * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 45338bb2c0a2..1b60f8ed904f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -838,7 +838,7 @@ public abstract class PackageManager { GET_DISABLED_COMPONENTS, GET_DISABLED_UNTIL_USED_COMPONENTS, GET_UNINSTALLED_PACKAGES, - FILTER_OUT_QUARANTINED_COMPONENTS, + MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface ComponentInfoFlagsBits {} @@ -863,7 +863,7 @@ public abstract class PackageManager { GET_DISABLED_UNTIL_USED_COMPONENTS, GET_UNINSTALLED_PACKAGES, MATCH_CLONE_PROFILE, - FILTER_OUT_QUARANTINED_COMPONENTS, + MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface ResolveInfoFlagsBits {} @@ -1252,12 +1252,13 @@ public abstract class PackageManager { */ // TODO(b/278553670) Unhide and update @links before launch. @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32; /** * @hide */ - public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L; + public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L; /** * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when @@ -4605,6 +4606,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/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 65f56f68ed3f..9869179d9686 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -132,11 +132,6 @@ public class ServiceInfo extends ComponentInfo * the {@link android.R.attr#foregroundServiceType} attribute. * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, * transfer over network between device and cloud. - * - * <p class="note"> - * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers - * that can be deferred until conditions are ideal for the app or device. - * </p> */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index ff21bfbff235..db12728cfb98 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -43,3 +43,18 @@ flag { description: "Feature flag to enable the feature to retrieve package info without installation." bug: "269149275" } + +flag { + name: "use_art_service_v2" + namespace: "package_manager_service" + description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module." + bug: "304741685" +} + +flag { + name: "sdk_lib_independence" + namespace: "package_manager_service" + description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable." + bug: "295827951" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index 1e60abb30011..7ada9469726b 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -231,7 +231,7 @@ public final class DomainVerificationUserState implements Parcelable { } /** - * Mapping of domain host to state, as defined by {@link DomainState}. + * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ed22284ae23d..1b37092f77b6 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2210,8 +2210,7 @@ public class Resources { * * <p>The best practices is to obtain metrics from * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from - * this API may be wrong if {@link Context#getResources()} is from - * non-{@link android.annotation.UiContext}. + * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}. * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()} * to build {@link android.app.Activity} UI elements especially when the * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}. diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig new file mode 100644 index 000000000000..0c2c0f494257 --- /dev/null +++ b/core/java/android/content/res/flags.aconfig @@ -0,0 +1,10 @@ +package: "android.content.res" + +flag { + name: "default_locale" + namespace: "resource_manager" + description: "Feature flag for default locale in LocaleConfig" + bug: "117306409" + # fixed_read_only or device wont boot because of permission issues accessing flags during boot + is_fixed_read_only: true +} diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java index c3440043a4c0..8f07d19a8592 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 db71624cbe89..755e6590a934 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/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 95ca0112b8b3..38fbd726a4fb 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -16,12 +16,14 @@ package android.credentials; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; +import android.credentials.flags.Flags; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -42,6 +44,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private final List<String> mCapabilities = new ArrayList<>(); @Nullable private final CharSequence mOverrideLabel; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; private final boolean mIsPrimary; @@ -59,6 +62,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsEnabled = builder.mIsEnabled; mIsPrimary = builder.mIsPrimary; mOverrideLabel = builder.mOverrideLabel; + mSettingsActivity = builder.mSettingsActivity; } /** Returns true if the service supports the given {@code credentialType}, false otherwise. */ @@ -104,10 +108,7 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } - /** - * Returns whether the provider is set as primary by the user. - * - */ + /** Returns whether the provider is set as primary by the user. */ public boolean isPrimary() { return mIsPrimary; } @@ -118,6 +119,23 @@ public final class CredentialProviderInfo implements Parcelable { return mSettingsSubtitle; } + /** + * Returns the settings activity. + * + * @hide + */ + @Nullable + @TestApi + @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED) + public CharSequence getSettingsActivity() { + // Add a manual check to make sure this returns null if + // the flag is not enabled. + if (!Flags.settingsActivityEnabled()) { + return null; + } + return mSettingsActivity; + } + /** Returns the component name for the service. */ @NonNull public ComponentName getComponentName() { @@ -133,6 +151,7 @@ public final class CredentialProviderInfo implements Parcelable { dest.writeBoolean(mIsPrimary); TextUtils.writeToParcel(mOverrideLabel, dest, flags); TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); + TextUtils.writeToParcel(mSettingsActivity, dest, flags); } @Override @@ -161,6 +180,9 @@ public final class CredentialProviderInfo implements Parcelable { + "settingsSubtitle=" + mSettingsSubtitle + ", " + + "settingsActivity=" + + mSettingsActivity + + ", " + "capabilities=" + String.join(",", mCapabilities) + "}"; @@ -174,6 +196,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsPrimary = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = @@ -196,6 +219,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private List<String> mCapabilities = new ArrayList<>(); private boolean mIsSystemProvider = false; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private boolean mIsEnabled = false; private boolean mIsPrimary = false; @Nullable private CharSequence mOverrideLabel = null; @@ -231,6 +255,16 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** + * Sets the settings activity. + * + * @hide + */ + public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + /** Sets a list of capabilities this provider service can support. */ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { mCapabilities.addAll(capabilities); diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java index 720c53ba51cd..0421d1f5aba0 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 056b18a51b6d..212f5716d041 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 746f2f23fd5c..b003e75b4e50 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 165f1810c894..827420f96fed 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 000000000000..564df039456b --- /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/SensorManager.java b/core/java/android/hardware/SensorManager.java index f033f9740b3d..bcf447b4267d 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -16,6 +16,7 @@ package android.hardware; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -27,6 +28,8 @@ import android.os.MemoryFile; import android.util.Log; import android.util.SparseArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -1809,6 +1812,41 @@ public abstract class SensorManager { protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor, boolean disable); + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION}) + public @interface DataInjectionMode {} + /** + * This mode is only used for testing purposes. Not all HALs support this mode. In this mode, + * the HAL ignores the sensor data provided by physical sensors and accepts the data that is + * injected from the SensorService as if it were the real sensor data. This mode is primarily + * used for testing various algorithms like vendor provided SensorFusion, Step Counter and + * Step Detector etc. Typically, in this mode, there is a client app which injects + * sensor data into the HAL. Normal apps can register and unregister for any sensor + * that supports injection. Registering to sensors that do not support injection will + * give an error. + * This is the default data injection mode. + * @hide + */ + public static final int DATA_INJECTION = 1; + /** + * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is + * delivered to all requesting apps rather than just the package allowed to inject data. + * This mode is only allowed to be used on development builds. + * @hide + */ + public static final int REPLAY_DATA_INJECTION = 3; + /** + * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a + * buffer in the platform and played back to all requesting apps. + * This is useful for playing back sensor data to test platform components without + * relying on the HAL to support data injection. + * @hide + */ + public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4; + /** * For testing purposes only. Not for third party applications. @@ -1833,13 +1871,47 @@ public abstract class SensorManager { */ @SystemApi public boolean initDataInjection(boolean enable) { - return initDataInjectionImpl(enable); + return initDataInjectionImpl(enable, DATA_INJECTION); + } + + /** + * For testing purposes only. Not for third party applications. + * + * Initialize data injection mode and create a client for data injection. SensorService should + * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or + * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in + * a Data Injection mode, use one of: + * + * <ul> + * <li>adb shell dumpsys sensorservice data_injection</li> + * <li>adb shell dumpsys sensorservice replay_data_injection package_name</li> + * <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li> + * </ul> + * + * Typically this is done using a host side test. This mode is expected to be used + * only for testing purposes. See {@link DataInjectionMode} for details of each data injection + * mode. Once this method succeeds, the test can call + * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL. + * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable" + * + * @param enable True to initialize a client in a data injection mode. + * False to clean up the native resources. + * + * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION. + * See {@link DataInjectionMode} for details. + * + * @return true if the HAL supports data injection and false + * otherwise. + * @hide + */ + public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) { + return initDataInjectionImpl(enable, mode); } /** * @hide */ - protected abstract boolean initDataInjectionImpl(boolean enable); + protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode); /** * For testing purposes only. Not for third party applications. @@ -1871,9 +1943,6 @@ public abstract class SensorManager { if (sensor == null) { throw new IllegalArgumentException("sensor cannot be null"); } - if (!sensor.isDataInjectionSupported()) { - throw new IllegalArgumentException("sensor does not support data injection"); - } if (values == null) { throw new IllegalArgumentException("sensor data cannot be null"); } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index dfd380233662..40e03dbb8b34 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -90,6 +90,8 @@ public class SystemSensorManager extends SensorManager { private static native void nativeGetRuntimeSensors( long nativeInstance, int deviceId, List<Sensor> list); private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); + private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance); + private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance); private static native int nativeCreateDirectChannel( long nativeInstance, int deviceId, long size, int channelType, int fd, @@ -384,20 +386,41 @@ public class SystemSensorManager extends SensorManager { } } - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { synchronized (sLock) { + boolean isDataInjectionModeEnabled = false; if (enable) { - boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); + switch (mode) { + case DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); + break; + case REPLAY_DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled( + mNativeInstance); + break; + case HAL_BYPASS_REPLAY_DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled( + mNativeInstance); + break; + default: + break; + } // The HAL does not support injection OR SensorService hasn't been set in DI mode. if (!isDataInjectionModeEnabled) { - Log.e(TAG, "Data Injection mode not enabled"); + Log.e(TAG, "The correct Data Injection mode has not been enabled"); return false; } + if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) { + // The inject event queue has been initialized for a different type of DI + // close it and create a new one + sInjectEventQueue.dispose(); + sInjectEventQueue = null; + } // Initialize a client for data_injection. if (sInjectEventQueue == null) { try { sInjectEventQueue = new InjectEventQueue( - mMainLooper, this, mContext.getPackageName()); + mMainLooper, this, mode, mContext.getPackageName()); } catch (RuntimeException e) { Log.e(TAG, "Cannot create InjectEventQueue: " + e); } @@ -421,6 +444,12 @@ public class SystemSensorManager extends SensorManager { Log.e(TAG, "Data injection mode not activated before calling injectSensorData"); return false; } + if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION + && !sensor.isDataInjectionSupported()) { + // DI mode and Replay DI mode require support from the sensor HAL + // HAL Bypass mode doesn't require this. + throw new IllegalArgumentException("sensor does not support data injection"); + } int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy, timestamp); // If there are any errors in data injection clean up the native resources. @@ -825,6 +854,8 @@ public class SystemSensorManager extends SensorManager { protected static final int OPERATING_MODE_NORMAL = 0; protected static final int OPERATING_MODE_DATA_INJECTION = 1; + protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3; + protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4; BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) { if (packageName == null) packageName = ""; @@ -1134,8 +1165,12 @@ public class SystemSensorManager extends SensorManager { } final class InjectEventQueue extends BaseEventQueue { - public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) { - super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName); + + private int mMode; + public InjectEventQueue(Looper looper, SystemSensorManager manager, + @DataInjectionMode int mode, String packageName) { + super(looper, manager, mode, packageName); + mMode = mode; } int injectSensorData(int handle, float[] values, int accuracy, long timestamp) { @@ -1161,6 +1196,10 @@ public class SystemSensorManager extends SensorManager { protected void removeSensorEvent(Sensor sensor) { } + + int getDataInjectionMode() { + return mMode; + } } protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..9c05dfc94ad4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -537,6 +537,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 * added on devices with multiple biometric sensors. diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..8eede472bec5 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 000000000000..7a0f4389ed60 --- /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 18c8d1bd3a1e..36606a135f3e 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/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 99f3d156cfa9..53a9a75fbca0 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -59,7 +59,8 @@ public final class BrightnessInfo implements Parcelable { @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = { BRIGHTNESS_MAX_REASON_NONE, - BRIGHTNESS_MAX_REASON_THERMAL + BRIGHTNESS_MAX_REASON_THERMAL, + BRIGHTNESS_MAX_REASON_POWER_IC }) @Retention(RetentionPolicy.SOURCE) public @interface BrightnessMaxReason {} @@ -74,6 +75,11 @@ public final class BrightnessInfo implements Parcelable { */ public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1; + /** + * Maximum brightness is restricted due to power throttling. + */ + public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2; + /** Brightness */ public final float brightness; @@ -144,6 +150,8 @@ public final class BrightnessInfo implements Parcelable { return "none"; case BRIGHTNESS_MAX_REASON_THERMAL: return "thermal"; + case BRIGHTNESS_MAX_REASON_POWER_IC: + return "power IC"; } return "invalid"; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f347909348c8..aeddd0c8d4b1 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -31,6 +31,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.KeyguardManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -775,7 +776,8 @@ public final class DisplayManager { */ public void registerDisplayListener(@NonNull DisplayListener listener, @Nullable Handler handler, @EventsMask long eventsMask) { - mGlobal.registerDisplayListener(listener, handler, eventsMask); + mGlobal.registerDisplayListener(listener, handler, eventsMask, + ActivityThread.currentPackageName()); } /** @@ -1819,6 +1821,12 @@ public final class DisplayManager { String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; /** + * Key for the power throttling data as a String formatted, from the display + * device config. + */ + String KEY_POWER_THROTTLING_DATA = "power_throttling_data"; + + /** * Key for new power controller feature flag. If enabled new DisplayPowerController will * be used. * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 6d6085b471d9..8decd50664b3 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -25,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.app.ActivityThread; import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -45,8 +46,11 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; +import android.sysprop.DisplayProperties; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; @@ -72,7 +76,13 @@ import java.util.concurrent.atomic.AtomicLong; */ public final class DisplayManagerGlobal { private static final String TAG = "DisplayManager"; - private static final boolean DEBUG = false; + + private static final String EXTRA_LOGGING_PACKAGE_NAME = + DisplayProperties.debug_vri_package().orElse(null); + private static String sCurrentPackageName = ActivityThread.currentPackageName(); + private static boolean sExtraDisplayListenerLogging = initExtraLogging(); + + private static final boolean DEBUG = false || sExtraDisplayListenerLogging; // True if display info and display ids should be cached. // @@ -130,6 +140,8 @@ public final class DisplayManagerGlobal { @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; + initExtraLogging(); + try { mWideColorSpace = ColorSpace.get( @@ -208,7 +220,7 @@ public final class DisplayManagerGlobal { registerCallbackIfNeededLocked(); - if (DEBUG) { + if (DEBUG || extraLogging()) { Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); } return info; @@ -321,12 +333,14 @@ public final class DisplayManagerGlobal { * If null, listener will use the handler for the current thread, and if still null, * the handler for the main thread. * If that is still null, a runtime exception will be thrown. + * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @Nullable Handler handler, @EventsMask long eventsMask) { + @Nullable Handler handler, @EventsMask long eventsMask, String packageName) { Looper looper = getLooperForHandler(handler); Handler springBoard = new Handler(looper); - registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask); + registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask, + packageName); } /** @@ -334,9 +348,11 @@ public final class DisplayManagerGlobal { * * @param listener The listener that will be called when display changes occur. * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. + * @param eventsMask Mask of events to be listened to. + * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @NonNull Executor executor, @EventsMask long eventsMask) { + @NonNull Executor executor, @EventsMask long eventsMask, String packageName) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -345,15 +361,22 @@ public final class DisplayManagerGlobal { throw new IllegalArgumentException("The set of events to listen to must not be empty."); } + if (extraLogging()) { + Slog.i(TAG, "Registering Display Listener: " + + Long.toBinaryString(eventsMask) + ", packageName: " + packageName); + } + synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { - mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask)); + mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask, + packageName)); registerCallbackIfNeededLocked(); } else { mDisplayListeners.get(index).setEventsMask(eventsMask); } updateCallbackIfNeededLocked(); + maybeLogAllDisplayListeners(); } } @@ -362,6 +385,10 @@ public final class DisplayManagerGlobal { throw new IllegalArgumentException("listener must not be null"); } + if (extraLogging()) { + Slog.i(TAG, "Unregistering Display Listener: " + listener); + } + synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index >= 0) { @@ -371,6 +398,18 @@ public final class DisplayManagerGlobal { updateCallbackIfNeededLocked(); } } + maybeLogAllDisplayListeners(); + } + + private void maybeLogAllDisplayListeners() { + if (!sExtraDisplayListenerLogging) { + return; + } + + Slog.i(TAG, "Currently Registered Display Listeners:"); + for (int i = 0; i < mDisplayListeners.size(); i++) { + Slog.i(TAG, i + ": " + mDisplayListeners.get(i)); + } } private static Looper getLooperForHandler(@Nullable Handler handler) { @@ -1148,15 +1187,20 @@ public final class DisplayManagerGlobal { private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Executor mExecutor; private AtomicLong mGenerationId = new AtomicLong(1); + private final String mPackageName; DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor, - @EventsMask long eventsMask) { + @EventsMask long eventsMask, String packageName) { mExecutor = executor; mListener = listener; mEventsMask = eventsMask; + mPackageName = packageName; } public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) { + if (extraLogging()) { + Slog.i(TAG, "Sending Display Event: " + eventToString(event)); + } long generationId = mGenerationId.get(); Message msg = Message.obtain(null, event, displayId, 0, info); mExecutor.execute(() -> { @@ -1177,6 +1221,14 @@ public final class DisplayManagerGlobal { } private void handleMessage(Message msg) { + if (extraLogging()) { + Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what) + + ", display=" + msg.arg1 + + ", mEventsMask=" + Long.toBinaryString(mEventsMask) + + ", mPackageName=" + mPackageName + + ", msg.obj=" + msg.obj + + ", listener=" + mListener.getClass() + ")"); + } if (DEBUG) { Trace.beginSection( "DisplayListenerDelegate(" + eventToString(msg.what) @@ -1193,6 +1245,10 @@ public final class DisplayManagerGlobal { if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { DisplayInfo newInfo = (DisplayInfo) msg.obj; if (newInfo != null && !newInfo.equals(mDisplayInfo)) { + if (extraLogging()) { + Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " + + newInfo); + } mDisplayInfo.copyFrom(newInfo); mListener.onDisplayChanged(msg.arg1); } @@ -1228,6 +1284,11 @@ public final class DisplayManagerGlobal { Trace.endSection(); } } + + @Override + public String toString() { + return "mask: {" + mEventsMask + "}, for " + mListener.getClass(); + } } /** @@ -1353,4 +1414,19 @@ public final class DisplayManagerGlobal { } return "UNKNOWN"; } + + + private static boolean initExtraLogging() { + if (sCurrentPackageName == null) { + sCurrentPackageName = ActivityThread.currentPackageName(); + sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME) + && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName); + } + return sExtraDisplayListenerLogging; + } + + private static boolean extraLogging() { + return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals( + sCurrentPackageName); + } } diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 02212968cdb0..02304b5ba4f3 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -1074,6 +1074,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ public interface FaceDetectionCallback { void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric); + + /** + * An error has occurred with face detection. + * + * This callback signifies that this operation has been completed, and + * no more callbacks should be expected. + */ + default void onDetectionError(int errorMsgId) {} } /** @@ -1373,6 +1381,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } else if (mRemovalCallback != null) { mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId, getErrorString(mContext, errMsgId, vendorCode)); + } else if (mFaceDetectionCallback != null) { + mFaceDetectionCallback.onDetectionError(errMsgId); + mFaceDetectionCallback = null; } } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 5bfda70f03de..935157a48a45 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -462,6 +462,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Invoked when a fingerprint has been detected. */ void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric); + + /** + * An error has occurred with fingerprint detection. + * + * This callback signifies that this operation has been completed, and + * no more callbacks should be expected. + */ + default void onDetectionError(int errorMsgId) {} } /** @@ -1484,6 +1492,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing ? mRemoveTracker.mSingleFingerprint : null; mRemovalCallback.onRemovalError(fp, clientErrMsgId, getErrorString(mContext, errMsgId, vendorCode)); + } else if (mFingerprintDetectionCallback != null) { + mFingerprintDetectionCallback.onDetectionError(errMsgId); + mFingerprintDetectionCallback = null; } } diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java index aa55e543dd48..05024ea95eda 100644 --- a/core/java/android/hardware/input/InputDeviceSensorManager.java +++ b/core/java/android/hardware/input/InputDeviceSensorManager.java @@ -644,7 +644,7 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, int mode) { return false; } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index ff1a6acd8e4e..7b8419e6c6f7 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1373,7 +1373,7 @@ public final class InputManager { public interface InputDeviceListener { /** * Called whenever an input device has been added to the system. - * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device. + * Use {@link #getInputDevice(int)} to get more information about the device. * * @param deviceId The id of the input device that was added. */ diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 582c5c0c4dd4..4c2bbc18f2db 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -3231,7 +3231,7 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Called when the user tapped or clicked an {@link android.widget.Editor}. + * Called when the user tapped or clicked an editor. * This can be useful when IME makes a decision of showing Virtual keyboard based on what * {@link MotionEvent#getToolType(int)} was used to click the editor. * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8482945dc1f0..e85b7bf1c91e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1854,7 +1854,7 @@ public abstract class BatteryStats { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public HistoryItem next; - // The time of this event in milliseconds, as per SystemClock.elapsedRealtime(). + // The time of this event in milliseconds, as per MonotonicClock.monotonicTime(). @UnsupportedAppUsage public long time; diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 49d7e8bc5632..32840d4b5837 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -300,6 +300,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis() */ + // TODO(b/298459065): switch to monotonic clock public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) { mFromTimestamp = fromTimestamp; mToTimestamp = toTimestamp; diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 086b0e5d4b05..58def6ef7961 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -143,10 +143,11 @@ public final class BugreportManager { */ public void onError(@BugreportErrorCode int errorCode) {} - /** Called when taking bugreport finishes successfully. + /** + * Called when taking bugreport finishes successfully. * * <p>This callback will be invoked if the - * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. + * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. */ public void onFinished() {} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 4174c1c13c94..fce715ade5af 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -2670,7 +2670,7 @@ public final class PowerManager { /** * Called when overall thermal throttling status changed. - * @param status defined in {@link android.os.Temperature}. + * @param status the status */ void onThermalStatusChanged(@ThermalStatus int status); } diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java index 5fb0df7febc1..8c5f2cd48f61 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 e76705917c7a..bb677d529507 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/RemoteException.java b/core/java/android/os/RemoteException.java index 970f4192c36c..ace5f7e8393f 100644 --- a/core/java/android/os/RemoteException.java +++ b/core/java/android/os/RemoteException.java @@ -66,7 +66,7 @@ public class RemoteException extends AndroidException { /** * Rethrow this exception when we know it came from the system server. This * gives us an opportunity to throw a nice clean - * {@link DeadSystemRuntimeException} signal to avoid spamming logs with + * {@code DeadSystemRuntimeException} signal to avoid spamming logs with * misleading stack traces. * <p> * Apps making calls into the system server may end up persisting internal diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 98f9dffaae13..5078dc351f6f 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -140,6 +140,31 @@ public final class VibrationAttributes implements Parcelable { */ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA; + /** @hide */ + @IntDef(prefix = { "CATEGORY_" }, value = { + CATEGORY_UNKNOWN, + CATEGORY_KEYBOARD, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Category {} + + /** + * Category value when the vibration category is unknown. + * + * @hide + */ + public static final int CATEGORY_UNKNOWN = 0x0; + + /** + * Category value for keyboard vibrations. + * + * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key + * press/release, for example. + * + * @hide + */ + public static final int CATEGORY_KEYBOARD = 1; + /** * @hide */ @@ -147,7 +172,8 @@ public final class VibrationAttributes implements Parcelable { FLAG_BYPASS_INTERRUPTION_POLICY, FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, FLAG_INVALIDATE_SETTINGS_CACHE, - FLAG_PIPELINED_EFFECT + FLAG_PIPELINED_EFFECT, + FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} @@ -167,6 +193,8 @@ public final class VibrationAttributes implements Parcelable { * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and * {@link AudioAttributes#FLAG_BYPASS_MUTE}. * + * <p>Only privileged apps can ignore user settings, and this flag will be ignored otherwise. + * * @hide */ public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1; @@ -199,12 +227,31 @@ public final class VibrationAttributes implements Parcelable { public static final int FLAG_PIPELINED_EFFECT = 1 << 3; /** + * Flag requesting that this vibration effect to be played without applying the user + * intensity setting to scale the vibration. + * + * <p>The user setting is still applied to enable/disable the vibration, but the vibration + * effect strength will not be scaled based on the enabled setting value. + * + * <p>This is intended to be used on scenarios where the system needs to enforce a specific + * strength for the vibration effect, regardless of the user preference. Only privileged apps + * can ignore user settings, and this flag will be ignored otherwise. + * + * <p>If you need to bypass the user setting when it's disabling vibrations then this also + * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set. + * + * @hide + */ + public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4; + + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF - | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT; + | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT + | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) { @@ -214,12 +261,14 @@ public final class VibrationAttributes implements Parcelable { private final int mUsage; private final int mFlags; private final int mOriginalAudioUsage; + private final int mCategory; private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage, - @Flag int flags) { + @Flag int flags, @Category int category) { mUsage = usage; mOriginalAudioUsage = audioUsage; mFlags = flags & FLAG_ALL_SUPPORTED; + mCategory = category; } /** @@ -248,6 +297,20 @@ public final class VibrationAttributes implements Parcelable { } /** + * Return the vibration category. + * + * <p>Vibration categories describe the source of the vibration, and it can be combined with + * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and + * category keyboard can be used to control keyboard haptic feedback independently. + * + * @hide + */ + @Category + public int getCategory() { + return mCategory; + } + + /** * Check whether a flag is set * @return true if a flag is set and false otherwise */ @@ -298,12 +361,14 @@ public final class VibrationAttributes implements Parcelable { dest.writeInt(mUsage); dest.writeInt(mOriginalAudioUsage); dest.writeInt(mFlags); + dest.writeInt(mCategory); } private VibrationAttributes(Parcel src) { mUsage = src.readInt(); mOriginalAudioUsage = src.readInt(); mFlags = src.readInt(); + mCategory = src.readInt(); } public static final @NonNull Parcelable.Creator<VibrationAttributes> @@ -326,12 +391,12 @@ public final class VibrationAttributes implements Parcelable { } VibrationAttributes rhs = (VibrationAttributes) o; return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage - && mFlags == rhs.mFlags; + && mFlags == rhs.mFlags && mCategory == rhs.mCategory; } @Override public int hashCode() { - return Objects.hash(mUsage, mOriginalAudioUsage, mFlags); + return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory); } @Override @@ -340,6 +405,7 @@ public final class VibrationAttributes implements Parcelable { + "mUsage=" + usageToString() + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage) + ", mFlags=" + mFlags + + ", mCategory=" + categoryToString() + '}'; } @@ -376,6 +442,23 @@ public final class VibrationAttributes implements Parcelable { } } + /** @hide */ + public String categoryToString() { + return categoryToString(mCategory); + } + + /** @hide */ + public static String categoryToString(@Category int category) { + switch (category) { + case CATEGORY_UNKNOWN: + return "UNKNOWN"; + case CATEGORY_KEYBOARD: + return "KEYBOARD"; + default: + return "unknown category " + category; + } + } + /** * Builder class for {@link VibrationAttributes} objects. * By default, all information is set to UNKNOWN. @@ -384,6 +467,7 @@ public final class VibrationAttributes implements Parcelable { private int mUsage = USAGE_UNKNOWN; private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN; private int mFlags = 0x0; + private int mCategory = CATEGORY_UNKNOWN; /** * Constructs a new Builder with the defaults. @@ -399,6 +483,7 @@ public final class VibrationAttributes implements Parcelable { mUsage = vib.mUsage; mOriginalAudioUsage = vib.mOriginalAudioUsage; mFlags = vib.mFlags; + mCategory = vib.mCategory; } } @@ -464,7 +549,8 @@ public final class VibrationAttributes implements Parcelable { * @return a new {@link VibrationAttributes} object */ public @NonNull VibrationAttributes build() { - VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags); + VibrationAttributes ans = new VibrationAttributes( + mUsage, mOriginalAudioUsage, mFlags, mCategory); return ans; } @@ -480,6 +566,19 @@ public final class VibrationAttributes implements Parcelable { } /** + * Sets the attribute describing the category of the corresponding vibration. + * + * @param category The category for the vibration + * @return the same Builder instance. + * + * @hide + */ + public @NonNull Builder setCategory(@Category int category) { + mCategory = category; + return this; + } + + /** * Sets only the flags specified in the bitmask, leaving the other supported flag values * unchanged in the builder. * diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 99c9925d9cb7..2fc24142acf2 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -184,6 +184,16 @@ public abstract class Vibrator { } /** + * Whether the keyboard vibration is enabled by default. + * + * @return {@code true} if the keyboard vibration is default enabled, {@code false} otherwise. + * @hide + */ + public boolean isDefaultKeyboardVibrationEnabled() { + return getConfig().isDefaultKeyboardVibrationEnabled(); + } + + /** * Return the ID of this vibrator. * * @return A non-negative integer representing the id of the vibrator controlled by this diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 37559b32e841..b7f2e065bac8 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -16,7 +16,7 @@ flag { flag { name: "remove_app_profiler_pss_collection" - namespace: "android_platform_power_optimization" + namespace: "backstage_power" description: "Replaces background PSS collection in AppProfiler with RSS" bug: "297542292" } diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index dfc43f46c57d..2d53341be654 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/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index bde334a6edc5..92e49676cbae 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -65,6 +65,8 @@ public class VibrationConfig { @VibrationIntensity private final int mDefaultRingVibrationIntensity; + private final boolean mDefaultKeyboardVibrationEnabled; + /** @hide */ public VibrationConfig(@Nullable Resources resources) { mHapticChannelMaxVibrationAmplitude = loadFloat(resources, @@ -76,6 +78,8 @@ public class VibrationConfig { mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources, com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false); + mDefaultKeyboardVibrationEnabled = loadBoolean(resources, + com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true); mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources, com.android.internal.R.integer.config_defaultAlarmVibrationIntensity); @@ -157,6 +161,14 @@ public class VibrationConfig { return mIgnoreVibrationsOnWirelessCharger; } + /** + * Whether keyboard vibration settings is enabled by default. + * @hide + */ + public boolean isDefaultKeyboardVibrationEnabled() { + return mDefaultKeyboardVibrationEnabled; + } + /** Get the default vibration intensity for given usage. */ @VibrationIntensity public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) { diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 88f62f327fdd..69d86a6604ad 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -27,3 +27,20 @@ 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" +} + +flag { + namespace: "haptics" + name: "keyboard_category_enabled" + description: "Enables the independent keyboard vibration settings feature" + bug: "289107579" +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index d8534dd5fbc1..3f06a91f6e5b 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -21,3 +21,17 @@ flag { description: "enable role controller in system server" bug: "302562590" } + +flag { + name: "set_next_attribution_source" + namespace: "permissions" + description: "enable AttributionSource.setNextAttributionSource" + bug: "304478648" +} + +flag { + name: "should_register_attribution_source" + namespace: "permissions" + description: "enable the shouldRegisterAttributionSource API" + bug: "305057691" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b19a034d4628..f0906b1e3c06 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1800,7 +1800,6 @@ public final class Settings { * Input: Nothing. * <p> * Output: Nothing. - * @see android.service.notification.NotificationAssistantService */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS = @@ -2507,8 +2506,8 @@ public final class Settings { * to the caller package. * <p> * <b>NOTE: </b> Applications should call - * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()} - * and only use this action to start an activity if they return {@code false}. + * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService( + * ComponentName)} and only use this action to start an activity if they return {@code false}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREDENTIAL_PROVIDER = @@ -5130,6 +5129,14 @@ public final class Settings { "hardware_haptic_feedback_intensity"; /** + * Whether keyboard vibration feedback is enabled. The value is boolean (1 or 0). + * + * @hide + */ + @Readable + public static final String KEYBOARD_VIBRATION_ENABLED = "keyboard_vibration_enabled"; + + /** * Ringer volume. This is used internally, changing this value will not * change the volume. See AudioManager. * @@ -12375,7 +12382,7 @@ public final class Settings { * Value to specify if the device's UTC system clock should be set automatically, e.g. using * telephony signals like NITZ, or other sources like GNSS or NTP. * - * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of + * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of * automatic time detection instead of directly observing this setting as it may be ignored * by the time_detector service under various conditions. * @@ -12388,7 +12395,7 @@ public final class Settings { * Value to specify if the device's time zone system property should be set automatically, * e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location. * - * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of + * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of * automatic time zone detection instead of directly observing this setting as it may be * ignored by the time_zone_detector service under various conditions. * diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 3fb94ee93c14..7ea74d375ffb 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -406,7 +406,7 @@ public final class FillResponse implements Parcelable { * * Call this when a field has been detected with a type. * - * Altough similiarly named with {@link setFieldClassificationIds}, + * Altough similiarly named with {@link #setFieldClassificationIds}, * it provides a different functionality - setFieldClassificationIds should * be used when a field is only suspected to be Autofillable. * This method should be used when a field is certainly Autofillable diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 954cc960665f..53706cd3f887 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -813,7 +813,7 @@ public final class SaveInfo implements Parcelable { * If no {@link #Builder(int, AutofillId[]) required ids}, * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE} * were set, Save Dialog will only be triggered if platform detection is enabled, which - * is indicated when {@link FillRequest.getHints()} is not empty. + * is indicated when {@link FillRequest#getHints()} is not empty. */ public SaveInfo build() { throwIfDestroyed(); diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index df934335e49d..5d96f69c2929 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 5ed06ac1ade7..ae6ca25a38ad 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 e755581638f5..c3524c5c85aa 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/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 0aa6a232d416..514d72252c3d 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -45,8 +45,8 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -135,8 +135,8 @@ public final class CredentialProviderInfoFactory { } /** - * Constructs an information instance of the credential provider for testing purposes. Does - * not run any verifications and passes parameters as is. + * Constructs an information instance of the credential provider for testing purposes. Does not + * run any verifications and passes parameters as is. */ @VisibleForTesting public static CredentialProviderInfo createForTests( @@ -151,7 +151,6 @@ public final class CredentialProviderInfoFactory { .setSystemProvider(isSystemProvider) .addCapabilities(capabilities) .build(); - } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -267,15 +266,21 @@ public final class CredentialProviderInfoFactory { allAttributes, com.android.internal.R.styleable.CredentialProvider); builder.setSettingsSubtitle( - afsAttributes.getString( + getAfsAttributeSafe( + afsAttributes, R.styleable.CredentialProvider_settingsSubtitle)); + builder.setSettingsActivity( + getAfsAttributeSafe( + afsAttributes, + R.styleable.CredentialProvider_settingsActivity)); } catch (Exception e) { - Slog.e(TAG, "Failed to get XML attr", e); + Slog.w(TAG, "Failed to get XML attr for metadata", e); } finally { if (afsAttributes != null) { afsAttributes.recycle(); } } + builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); } else { Slog.w(TAG, "Meta-data does not start with credential-provider-service tag"); @@ -287,6 +292,21 @@ public final class CredentialProviderInfoFactory { return builder; } + private static @Nullable String getAfsAttributeSafe( + @Nullable TypedArray afsAttributes, int resId) { + if (afsAttributes == null) { + return null; + } + + try { + return afsAttributes.getString(resId); + } catch (Exception e) { + Slog.w(TAG, "Failed to get XML attr from afs attributes", e); + } + + return null; + } + private static List<String> parseXmlProviderOuterCapabilities( XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { final List<String> capabilities = new ArrayList<>(); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 1cfff14b2d7c..759953e4aca4 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -896,8 +896,7 @@ public abstract class NotificationListenerService extends Service { * <p>This method will throw a security exception if you don't have access to notifications * for the given user.</p> * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated - * device} or be the {@link NotificationAssistantService notification assistant} in order to - * use this method. + * device} or be the notification assistant in order to use this method. * * @param pkg The package to retrieve channels for. */ @@ -920,8 +919,7 @@ public abstract class NotificationListenerService extends Service { * <p>This method will throw a security exception if you don't have access to notifications * for the given user.</p> * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated - * device} or be the {@link NotificationAssistantService notification assistant} in order to - * use this method. + * device} or be the notification assistant in order to use this method. * * @param pkg The package to retrieve channel groups for. */ @@ -2001,7 +1999,7 @@ public abstract class NotificationListenerService extends Service { /** * Returns a list of smart {@link Notification.Action} that can be added by the - * {@link NotificationAssistantService} + * notification assistant. */ public @NonNull List<Notification.Action> getSmartActions() { return mSmartActions == null ? Collections.emptyList() : mSmartActions; @@ -2022,8 +2020,7 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns a list of smart replies that can be added by the - * {@link NotificationAssistantService} + * Returns a list of smart replies that can be added by the notification assistant. */ public @NonNull List<CharSequence> getSmartReplies() { return mSmartReplies == null ? Collections.emptyList() : mSmartReplies; diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 79c8fb4a6620..c82a4cabaddd 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -26,6 +26,7 @@ import android.system.ErrnoException; import android.system.OsConstants; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; @@ -138,6 +139,7 @@ public class NotificationRankingUpdate implements Parcelable { * * @hide */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public final boolean isFdNotNullAndClosed() { return mRankingMapFd != null && mRankingMapFd.getFd() == -1; } diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java index 4a4fd041fb19..bf129c551605 100644 --- a/core/java/android/service/quickaccesswallet/WalletCard.java +++ b/core/java/android/service/quickaccesswallet/WalletCard.java @@ -372,9 +372,9 @@ public final class WalletCard implements Parcelable { } /** - * Set of locations this card might be useful at. If {@link - * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be - * shown to the user when a user is near one of these locations. + * Set of locations this card might be useful at. If + * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is + * enabled, the card might be shown to the user when a user is near one of these locations. */ @NonNull public Builder setCardLocations(@NonNull List<Location> cardLocations) { diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java index 7af7fe6108e6..db97d4f52643 100644 --- a/core/java/android/service/voice/AbstractDetector.java +++ b/core/java/android/service/voice/AbstractDetector.java @@ -199,8 +199,12 @@ abstract class AbstractDetector implements HotwordDetector { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + Consumer<AbstractDetector> onDestroyListener; synchronized (mLock) { - mOnDestroyListener.accept(this); + onDestroyListener = mOnDestroyListener; + } + if (onDestroyListener != null) { + onDestroyListener.accept(this); } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 3f41c56ac7f1..d2806217a276 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -520,7 +520,7 @@ public class VoiceInteractionService extends Service { @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -546,6 +546,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, + // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the + // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -612,6 +616,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* modulProperties */ null, /* executor= */ null, callback); @@ -663,7 +672,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -690,6 +703,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle, + // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index cabcae30851f..d40b39e18edb 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1809,7 +1809,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * in milliseconds of the KeyEvent that triggered Assistant and * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID) * referring to the device that sent the request. Starting from Android 14, the system will - * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the + * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the * application should handle null case before Android 14. * @param showFlags The show flags originally provided to * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index bb5dd7f0cdfe..3ed13bbeeaad 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -536,7 +536,16 @@ public class SpeechRecognizer { @MainThread public void setRecognitionListener(RecognitionListener listener) { checkIsCalledFromMainThread(); - putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + if (mListener.mInternalListener == null) { + // This shortcut is needed because otherwise, if there's an error connecting, it never + // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does + // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same + // place does not get forwarded anywhere. + // Thread-wise, this is safe as both this method and the handler are on the UI thread. + handleChangeListener(listener); + } else { + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } } /** diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 8862f1d74ab1..a0cd0748f509 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -1274,7 +1274,7 @@ public class DynamicLayout extends Layout { } /** - * Gets the {@link LineBreakconfig} used in this DynamicLayout. + * Gets the {@link LineBreakConfig} used in this DynamicLayout. * Use this only to consult the LineBreakConfig's properties and not * to change them. * diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java index be002f3102d3..b0a70eae902a 100644 --- a/core/java/android/text/WordSegmentFinder.java +++ b/core/java/android/text/WordSegmentFinder.java @@ -24,7 +24,7 @@ import android.text.method.WordIterator; /** * Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are - * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in + * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in * any text segments. * * <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello", diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 2906d86f803d..766e924c994e 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -200,6 +200,12 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION = "settings_remote_device_credential_validation"; + /** + * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine". + * @hide + */ + public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE = + "settings_treat_pause_as_quarantine"; private static final Map<String, String> DEFAULT_FLAGS; @@ -247,6 +253,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); // TODO: b/298454866 Replace with Trunk Stable Feature Flag DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); + DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false"); } private static final Set<String> PERSISTENT_FLAGS; @@ -264,6 +271,7 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2); PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM); + PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE); } /** diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index a89f79540c65..dc41b70d1683 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -52,6 +52,12 @@ public final class ContentRecordingSession implements Parcelable { */ public static final int RECORD_CONTENT_TASK = 1; + /** Full screen sharing (app is not selected). */ + public static final int TARGET_UID_FULL_SCREEN = -1; + + /** Can't report (e.g. side loaded app). */ + public static final int TARGET_UID_UNKNOWN = -2; + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. @@ -89,27 +95,36 @@ public final class ContentRecordingSession implements Parcelable { */ private boolean mWaitingForConsent = false; + /** UID of the package that is captured if selected. */ + private int mTargetUid = TARGET_UID_UNKNOWN; + /** * Default instance, with recording the display. */ private ContentRecordingSession() { } - /** - * Returns an instance initialized for recording the indicated display. - */ + /** Returns an instance initialized for recording the indicated display. */ public static ContentRecordingSession createDisplaySession(int displayToMirror) { - return new ContentRecordingSession().setDisplayToRecord(displayToMirror) - .setContentToRecord(RECORD_CONTENT_DISPLAY); + return new ContentRecordingSession() + .setDisplayToRecord(displayToMirror) + .setContentToRecord(RECORD_CONTENT_DISPLAY) + .setTargetUid(TARGET_UID_FULL_SCREEN); } - /** - * Returns an instance initialized for task recording. - */ + /** Returns an instance initialized for task recording. */ public static ContentRecordingSession createTaskSession( @NonNull IBinder taskWindowContainerToken) { - return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK) - .setTokenToRecord(taskWindowContainerToken); + return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN); + } + + /** Returns an instance initialized for task recording. */ + public static ContentRecordingSession createTaskSession( + @NonNull IBinder taskWindowContainerToken, int targetUid) { + return new ContentRecordingSession() + .setContentToRecord(RECORD_CONTENT_TASK) + .setTokenToRecord(taskWindowContainerToken) + .setTargetUid(targetUid); } /** @@ -175,13 +190,33 @@ public final class ContentRecordingSession implements Parcelable { } } + @IntDef(prefix = "TARGET_UID_", value = { + TARGET_UID_FULL_SCREEN, + TARGET_UID_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface TargetUid {} + + @DataClass.Generated.Member + public static String targetUidToString(@TargetUid int value) { + switch (value) { + case TARGET_UID_FULL_SCREEN: + return "TARGET_UID_FULL_SCREEN"; + case TARGET_UID_UNKNOWN: + return "TARGET_UID_UNKNOWN"; + default: return Integer.toHexString(value); + } + } + @DataClass.Generated.Member /* package-private */ ContentRecordingSession( int virtualDisplayId, @RecordContent int contentToRecord, int displayToRecord, @Nullable IBinder tokenToRecord, - boolean waitingForConsent) { + boolean waitingForConsent, + int targetUid) { this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -196,6 +231,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -251,6 +287,14 @@ public final class ContentRecordingSession implements Parcelable { } /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public int getTargetUid() { + return mTargetUid; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @@ -314,6 +358,15 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull ContentRecordingSession setTargetUid( int value) { + mTargetUid = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -325,7 +378,8 @@ public final class ContentRecordingSession implements Parcelable { "contentToRecord = " + recordContentToString(mContentToRecord) + ", " + "displayToRecord = " + mDisplayToRecord + ", " + "tokenToRecord = " + mTokenToRecord + ", " + - "waitingForConsent = " + mWaitingForConsent + + "waitingForConsent = " + mWaitingForConsent + ", " + + "targetUid = " + mTargetUid + " }"; } @@ -346,7 +400,8 @@ public final class ContentRecordingSession implements Parcelable { && mContentToRecord == that.mContentToRecord && mDisplayToRecord == that.mDisplayToRecord && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord) - && mWaitingForConsent == that.mWaitingForConsent; + && mWaitingForConsent == that.mWaitingForConsent + && mTargetUid == that.mTargetUid; } @Override @@ -361,6 +416,7 @@ public final class ContentRecordingSession implements Parcelable { _hash = 31 * _hash + mDisplayToRecord; _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord); _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent); + _hash = 31 * _hash + mTargetUid; return _hash; } @@ -378,6 +434,7 @@ public final class ContentRecordingSession implements Parcelable { dest.writeInt(mContentToRecord); dest.writeInt(mDisplayToRecord); if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord); + dest.writeInt(mTargetUid); } @Override @@ -397,6 +454,7 @@ public final class ContentRecordingSession implements Parcelable { int contentToRecord = in.readInt(); int displayToRecord = in.readInt(); IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder(); + int targetUid = in.readInt(); this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -412,6 +470,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -442,6 +501,7 @@ public final class ContentRecordingSession implements Parcelable { private int mDisplayToRecord; private @Nullable IBinder mTokenToRecord; private boolean mWaitingForConsent; + private int mTargetUid; private long mBuilderFieldsSet = 0L; @@ -513,10 +573,21 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull Builder setTargetUid(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mTargetUid = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ContentRecordingSession build() { checkNotUsed(); - mBuilderFieldsSet |= 0x20; // Mark builder used + mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mVirtualDisplayId = INVALID_DISPLAY; @@ -533,17 +604,21 @@ public final class ContentRecordingSession implements Parcelable { if ((mBuilderFieldsSet & 0x10) == 0) { mWaitingForConsent = false; } + if ((mBuilderFieldsSet & 0x20) == 0) { + mTargetUid = TARGET_UID_UNKNOWN; + } ContentRecordingSession o = new ContentRecordingSession( mVirtualDisplayId, mContentToRecord, mDisplayToRecord, mTokenToRecord, - mWaitingForConsent); + mWaitingForConsent, + mTargetUid); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x20) != 0) { + if ((mBuilderFieldsSet & 0x40) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -551,10 +626,10 @@ public final class ContentRecordingSession implements Parcelable { } @DataClass.Generated( - time = 1683628463074L, + time = 1697456140720L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java", - inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0b2b6ce33666..506945501609 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.KeyguardManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; @@ -1366,7 +1367,8 @@ public final class Display { // form of the larger DISPLAY_CHANGED event mGlobal.registerDisplayListener(toRegister, executor, DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED - | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED, + ActivityThread.currentPackageName()); } } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..11180aef4479 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 c35b6907cd9d..9f886c826174 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -778,7 +778,8 @@ public final class InputDevice implements Parcelable { * Each gamepad or joystick is given a unique, positive controller number when initially * configured by the system. This number may change due to events such as device disconnects / * reconnects or user initiated reassignment. Any change in number will trigger an event that - * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}. + * can be observed by registering an + * {@link android.hardware.input.InputManager.InputDeviceListener}. * </p> * <p> * All input devices which are not gamepads or joysticks will be assigned a controller number diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 78716f5646f6..0ba414817247 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -17,7 +17,6 @@ package android.view; import android.annotation.FlaggedApi; -import android.annotation.NonNull; import android.view.flags.Flags; /** @@ -43,7 +42,8 @@ import android.view.flags.Flags; * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}. * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an - * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input + * {@link android.hardware.input.InputManager} instance + * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input * device IDs). * * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b080b7115728..2f3d73a36425 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -263,7 +263,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj, long nativeObject, int compatibility); private static native void nativeSetFrameRateCategory( - long transactionObj, long nativeObject, int category); + long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly); private static native void nativeSetFrameRateSelectionStrategy( long transactionObj, long nativeObject, int strategy); private static native long nativeGetHandle(long nativeObject); @@ -3709,6 +3709,10 @@ public final class SurfaceControl implements Parcelable { * @param sc The SurfaceControl to specify the frame rate category of. * @param category The frame rate category of this surface. The category value may influence * the system's choice of display frame rate. + * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not + * change if changing it would cause jank. Else {@code false}. + * This parameter is ignored when {@code category} is + * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}. * * @return This transaction object. * @@ -3717,10 +3721,10 @@ public final class SurfaceControl implements Parcelable { * @hide */ @NonNull - public Transaction setFrameRateCategory( - @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) { + public Transaction setFrameRateCategory(@NonNull SurfaceControl sc, + @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) { checkPreconditions(sc); - nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category); + nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly); return this; } @@ -4266,8 +4270,7 @@ public final class SurfaceControl implements Parcelable { * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in * this API as well. * <p> - * @param sc The {@link SurfaceControl} to set the - * {@link TrustedPresentationCallback} on + * @param sc The {@link SurfaceControl} to set the callback on * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to * invoke the callback. * @param executor The {@link Executor} where the callback will be invoked on. @@ -4300,10 +4303,9 @@ public final class SurfaceControl implements Parcelable { } /** - * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl} + * Clears the callback for a specific {@link SurfaceControl} * - * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be - * cleared from + * @param sc The SurfaceControl that the callback should be cleared from * @return This transaction */ @NonNull diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 67ac811287cb..0f35048cac67 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -78,6 +78,11 @@ public class SurfaceControlRegistry { for (int i = 0; i < size; i++) { final Map.Entry<SurfaceControl, Long> entry = entries.get(i); final SurfaceControl sc = entry.getKey(); + if (sc == null) { + // Just skip if the key has since been removed from the weak hash map + continue; + } + final long timeRegistered = entry.getValue(); pw.print(" "); pw.print(sc.getName()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16318e0a3f49..49b16c7697c9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,6 +19,8 @@ package android.view; import static android.content.res.Resources.ID_NULL; import static android.os.Trace.TRACE_TAG_APP; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -27,6 +29,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.toolkitSetFrameRate; import static android.view.flags.Flags.viewVelocityApi; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; @@ -114,6 +117,7 @@ import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; @@ -5509,6 +5513,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private ViewTranslationResponse mViewTranslationResponse; /** + * A threshold value to determine the frame rate category of the View based on the size. + */ + private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -9308,6 +9317,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setAutofillType(autofillType); structure.setAutofillHints(getAutofillHints()); structure.setAutofillValue(getAutofillValue()); + structure.setIsCredential(isCredential()); } structure.setImportantForAutofill(getImportantForAutofill()); structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes()); @@ -20182,6 +20192,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); + // Reset content capture caches mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK; mContentCaptureSessionCached = false; @@ -20284,6 +20297,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void damageInParent() { if (mParent != null && mAttachInfo != null) { + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); mParent.onDescendantInvalidated(this, this); } } @@ -32980,6 +32995,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } + private float getSizePercentage() { + if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { + return 0; + } + + DisplayMetrics displayMetrics = mResources.getDisplayMetrics(); + int screenSize = displayMetrics.widthPixels + * displayMetrics.heightPixels; + int viewSize = getWidth() * getHeight(); + + if (screenSize == 0 || viewSize == 0) { + return 0f; + } + return (float) viewSize / screenSize; + } + + private int calculateFrameRateCategory() { + float sizePercentage = getSizePercentage(); + + if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { + return FRAME_RATE_CATEGORY_LOW; + } else { + return FRAME_RATE_CATEGORY_NORMAL; + } + } + + private void votePreferredFrameRate() { + // use toolkitSetFrameRate flag to gate the change + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) { + viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); + } + } + /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e111dc8088de..9d15c78a6e9e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,6 +24,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -74,7 +76,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; @@ -87,6 +92,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.view.flags.Flags.toolkitSetFrameRate; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -732,6 +738,7 @@ public final class ViewRootImpl implements ViewParent, private SurfaceControl mBoundsLayer; private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final Transaction mTransaction = new Transaction(); + private final Transaction mFrameRateTransaction = new Transaction(); @UnsupportedAppUsage boolean mAdded; @@ -955,6 +962,34 @@ public final class ViewRootImpl implements ViewParent, private AccessibilityWindowAttributes mAccessibilityWindowAttributes; + /* + * for Variable Refresh Rate project + */ + + // The preferred frame rate category of the view that + // could be updated on a frame-by-frame basis. + private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate category of the last frame that + // could be used to lower frame rate after touch boost + private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate of the view that is mainly used for + // touch boosting, view velocity handling, and TextureView. + private float mPreferredFrameRate = 0; + // Used to check if there were any view invalidations in + // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). + private boolean mHasInvalidation = false; + // Used to check if it is in the touch boosting period. + private boolean mIsFrameRateBoosting = false; + // Used to check if there is a message in the message queue + // for idleness handling. + private boolean mHasIdledMessage = false; + // time for touch boost period. + private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; + // time for checking idle status periodically. + private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; + // time for revaluating the idle status before lowering the frame rate. + private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500; + /** * A temporary object used so relayoutWindow can return the latest SyncSeqId * system. The SyncSeqId system was designed to work without synchronous relayout @@ -1549,7 +1584,8 @@ public final class ViewRootImpl implements ViewParent, mHandler, DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, + mBasePackageName); } /** @@ -1730,7 +1766,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 +3385,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(); @@ -3917,6 +3953,12 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_NONE; } } + + // For the variable refresh rate project. + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; } private void createSyncIfNeeded() { @@ -5652,7 +5694,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 +5709,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(); @@ -5965,6 +6011,8 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38; + private static final int MSG_TOUCH_BOOST_TIMEOUT = 39; + private static final int MSG_CHECK_INVALIDATION_IDLE = 40; final class ViewRootHandler extends Handler { @Override @@ -6260,6 +6308,32 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_TOUCH_BOOST_TIMEOUT: + /** + * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). + */ + mIsFrameRateBoosting = false; + setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, + mLastPreferredFrameRateCategory)); + break; + case MSG_CHECK_INVALIDATION_IDLE: + if (!mHasInvalidation && !mIsFrameRateBoosting) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mHasIdledMessage = false; + } else { + /** + * If there is no invalidation within a certain period, + * we consider the display is idled. + * We then set the frame rate catetogry to NO_PREFERENCE. + * Note that SurfaceFlinger also has a mechanism to lower the refresh rate + * if there is no updates of the buffer. + */ + mHasInvalidation = false; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_REEVALUATE_TIME); + } + break; } } } @@ -7202,6 +7276,7 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + final int action = event.getAction(); boolean handled = mHandwritingInitiator.onTouchEvent(event); if (handled) { // If handwriting is started, toolkit doesn't receive ACTION_UP. @@ -7222,6 +7297,22 @@ public final class ViewRootImpl implements ViewParent, scheduleConsumeBatchedInputImmediately(); } } + + // For the variable refresh rate project + if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { + // set the frame rate to the maximum value. + mIsFrameRateBoosting = true; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + } + /** + * We want to lower the refresh rate when MotionEvent.ACTION_UP, + * MotionEvent.ACTION_CANCEL is detected. + * Not using ACTION_MOVE to avoid checking and sending messages too frequently. + */ + if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL)) { + sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); + } return handled ? FINISH_HANDLED : FORWARD; } @@ -10543,6 +10634,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, @@ -11803,4 +11896,94 @@ public final class ViewRootImpl implements ViewParent, @NonNull Consumer<Boolean> listener) { t.clearTrustedPresentationCallback(getSurfaceControl()); } + + private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { + if (!shouldSetFrameRateCategory()) { + return; + } + + int frameRateCategory = mIsFrameRateBoosting + ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + + try { + mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, + frameRateCategory, false).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate category", e); + } + + if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) { + // Check where the display is idled periodically. + // If so, set the frame rate category to NO_PREFERENCE + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS); + mHasIdledMessage = true; + } + } + + private void setPreferredFrameRate(float preferredFrameRate) { + if (!shouldSetFrameRate()) { + return; + } + + try { + mFrameRateTransaction.setFrameRate(mSurfaceControl, + preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate", e); + } + } + + private void sendDelayedEmptyMessage(int message, int delayedTime) { + mHandler.removeMessages(message); + + mHandler.sendEmptyMessageDelayed(message, delayedTime); + } + + private boolean shouldSetFrameRateCategory() { + // use toolkitSetFrameRate flag to gate the change + return mSurface.isValid() && toolkitSetFrameRate(); + } + + private boolean shouldSetFrameRate() { + // use toolkitSetFrameRate flag to gate the change + return mPreferredFrameRate > 0 && toolkitSetFrameRate(); + } + + private boolean shouldTouchBoost(int motionEventAction, int windowType) { + boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN + || motionEventAction == MotionEvent.ACTION_MOVE + || motionEventAction == MotionEvent.ACTION_UP; + boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION + || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION; + // use toolkitSetFrameRate flag to gate the change + return desiredAction && desiredType && toolkitSetFrameRate(); + } + + /** + * Allow Views to vote for the preferred frame rate category + * + * @param frameRateCategory the preferred frame rate category of a View + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void votePreferredFrameRateCategory(int frameRateCategory) { + mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); + mHasInvalidation = true; + } + + /** + * Get the value of mPreferredFrameRateCategory + */ + @VisibleForTesting + public int getPreferredFrameRateCategory() { + return mPreferredFrameRateCategory; + } + + /** + * Get the value of mPreferredFrameRate + */ + @VisibleForTesting + public float getPreferredFrameRate() { + return mPreferredFrameRate; + } } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 2c2ae06e9186..bb2c7c8b198b 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -397,6 +397,13 @@ public abstract class ViewStructure { public void setImportantForAutofill(@AutofillImportance int mode) {} /** + * Sets whether the node is a credential. See {@link View#isCredential}. + * + * @hide + */ + public void setIsCredential(boolean isCredential) {} + + /** * Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}. * * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2f04b0c695da..87537fbc9961 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; @@ -1330,6 +1332,47 @@ public abstract class Window { } /** + * <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 * display switches back to standard image processing. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 08f9c8cdd2a8..4f03ce98ccac 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -17,6 +17,7 @@ package android.view; import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; +import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST; import static android.view.View.STATUS_BAR_DISABLE_BACK; import static android.view.View.STATUS_BAR_DISABLE_CLOCK; import static android.view.View.STATUS_BAR_DISABLE_EXPAND; @@ -83,6 +84,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 +1686,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 +1694,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 +3116,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 @@ -3904,6 +3906,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMinDisplayRefreshRate; @@ -3913,6 +3916,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMaxDisplayRefreshRate; @@ -4315,6 +4319,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 +4534,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 +4549,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; @@ -4717,6 +4724,39 @@ public interface WindowManager extends ViewManager { } /** + * <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}, * but instead of dimmed, the content behind the window will be blurred (or combined with @@ -4866,6 +4906,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 +4978,7 @@ public interface WindowManager extends ViewManager { forciblyShownTypes = in.readInt(); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); + mDesiredHdrHeadroom = in.readFloat(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5197,6 +5239,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 +5471,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 7ad43c76efaa..26298bc645ad 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -47,7 +47,6 @@ import java.util.function.Supplier; * @see WindowInsets#getInsets(int) * @see WindowManager#getCurrentWindowMetrics() * @see WindowManager#getMaximumWindowMetrics() - * @see android.annotation.UiContext */ public final class WindowMetrics { @NonNull @@ -99,8 +98,7 @@ public final class WindowMetrics { } /** - * Returns the bounds of the area associated with this window or - * {@link android.annotation.UiContext}. + * Returns the bounds of the area associated with this window or {@code UiContext}. * <p> * <b>Note that the size of the reported bounds can have different size than * {@link Display#getSize(Point)}.</b> This method reports the window size including all system @@ -133,7 +131,7 @@ public final class WindowMetrics { /** * Returns the {@link WindowInsets} of the area associated with this window or - * {@link android.annotation.UiContext}. + * {@code UiContext}. * * @return the {@link WindowInsets} of the visual area. */ @@ -146,9 +144,8 @@ public final class WindowMetrics { } /** - * Returns the density of the area associated with this window or - * {@link android.annotation.UiContext}, which uses the same units as - * {@link android.util.DisplayMetrics#density}. + * Returns the density of the area associated with this window or {@code UiContext}, + * which uses the same units as {@link android.util.DisplayMetrics#density}. * * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 60f46e60ec9e..12ce0f47c460 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -1731,6 +1731,9 @@ public final class AccessibilityInteractionClient @Override public void sendAttachOverlayResult( @AccessibilityService.AttachOverlayResult int result, int interactionId) { + if (!Flags.a11yOverlayCallbacks()) { + return; + } synchronized (mInstanceLock) { if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { final Pair<Executor, IntConsumer> pair = diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index e6a8b7827b04..43bfe139c223 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1562,9 +1562,8 @@ public class AccessibilityNodeInfo implements Parcelable { * describes the action. * </p> * <p> - * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence, + * AccessibilityViewCommand)} to register an action directly on the view. * <p> * <strong>Note:</strong> Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. @@ -5167,8 +5166,7 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> * <aside class="note"> * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * CharSequence, AccessibilityViewCommand)} to register an action directly on the view. * </p> */ public static final class AccessibilityAction implements Parcelable { diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 09b2f9c1ee15..fa0052cf664a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -99,7 +99,6 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ - @TestApi public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 85dadd4a061d..cc612ed93b2f 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" bug: "303871725" } + +flag { + name: "a11y_overlay_callbacks" + namespace: "accessibility" + description: "Whether to allow the passing of result callbacks when attaching a11y overlays." + bug: "304478691" +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a76780d18f71..32256b9b09c8 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -56,7 +56,7 @@ public class AnimationUtils { private static final int SEQUENTIALLY = 1; /** - * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, + * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, * this change ID enables to use expectedPresentationTime instead of the frameTime * for the frame start time . * diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 2c7d326587c7..42b3e38b544f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -60,7 +60,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -377,6 +379,30 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE = "content_protection_buffer_size"; + /** + * Sets the config for content protection required groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = + "content_protection_required_groups_config"; + + /** + * Sets the config for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = + "content_protection_optional_groups_config"; + + /** + * Sets the threshold for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = + "content_protection_optional_groups_threshold"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -417,6 +443,18 @@ public final class ContentCaptureManager { public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000; /** @hide */ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = ""; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = ""; + /** @hide */ + public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0; private final Object mLock = new Object(); diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index dc3d32317ded..bb815c0e8317 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -183,7 +183,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10; /** - * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps + * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps * the virtual children with a pair of view tree appearing and view tree appeared events. */ @ChangeId diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2241fd5dc37a..b44d6a496058 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -317,16 +317,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - // Should not be possible for mComponentName to be null here but check anyway - if (mManager.mOptions.contentProtectionOptions.enableReceiver - && mManager.getContentProtectionEventBuffer() != null - && mComponentName != null) { + if (isContentProtectionEnabled()) { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mManager.getContentProtectionEventBuffer(), mHandler, mSystemServerInterface, - mComponentName.getPackageName()); + mComponentName.getPackageName(), + mManager.mOptions.contentProtectionOptions); } else { mContentProtectionEventProcessor = null; } @@ -956,4 +954,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; } + + @UiThread + private boolean isContentProtectionEnabled() { + // Should not be possible for mComponentName to be null here but check anyway + // Should not be possible for groups to be empty if receiver is enabled but check anyway + return mManager.mOptions.contentProtectionOptions.enableReceiver + && mManager.getContentProtectionEventBuffer() != null + && mComponentName != null + && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() + || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); + } } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index b44abf3eb04d..aaf90bd00535 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -19,9 +19,9 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.text.InputType; import android.util.Log; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -33,10 +33,10 @@ import com.android.internal.util.RingBuffer; import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Stream; /** * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow. @@ -47,33 +47,13 @@ public class ContentProtectionEventProcessor { private static final String TAG = "ContentProtectionEventProcessor"; - private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES = - Collections.unmodifiableList( - Arrays.asList( - InputType.TYPE_NUMBER_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, - InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)); - - private static final List<String> PASSWORD_TEXTS = - Collections.unmodifiableList( - Arrays.asList("password", "pass word", "code", "pin", "credential")); - - private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS = - Collections.unmodifiableList( - Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in")); - private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3); - private static final String ANDROID_CLASS_NAME_PREFIX = "android."; - private static final Set<Integer> EVENT_TYPES_TO_STORE = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - ContentCaptureEvent.TYPE_VIEW_APPEARED, - ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, - ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); + Set.of( + ContentCaptureEvent.TYPE_VIEW_APPEARED, + ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, + ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @@ -85,11 +65,7 @@ public class ContentProtectionEventProcessor { @NonNull private final String mPackageName; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mPasswordFieldDetected = false; - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mSuspiciousTextDetected = false; + @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @Nullable @@ -97,15 +73,32 @@ public class ContentProtectionEventProcessor { private int mResetLoginRemainingEventsToProcess; + private boolean mAnyGroupFound = false; + + // Ordered by priority + private final List<SearchGroup> mGroupsRequired; + + // Ordered by priority + private final List<SearchGroup> mGroupsOptional; + + // Ordered by priority + private final List<SearchGroup> mGroupsAll; + public ContentProtectionEventProcessor( @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull Handler handler, @NonNull IContentCaptureManager contentCaptureManager, - @NonNull String packageName) { + @NonNull String packageName, + @NonNull ContentCaptureOptions.ContentProtectionOptions options) { mEventBuffer = eventBuffer; mHandler = handler; mContentCaptureManager = contentCaptureManager; mPackageName = packageName; + mOptions = options; + mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList(); + mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList(); + mGroupsAll = + Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList(); } /** Main entry point for {@link ContentCaptureEvent} processing. */ @@ -130,9 +123,31 @@ public class ContentProtectionEventProcessor { @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { - mPasswordFieldDetected |= isPasswordField(event); - mSuspiciousTextDetected |= isSuspiciousText(event); - if (mPasswordFieldDetected && mSuspiciousTextDetected) { + ViewNode viewNode = event.getViewNode(); + String eventText = ContentProtectionUtils.getEventTextLower(event); + String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode); + String hintText = ContentProtectionUtils.getHintTextLower(viewNode); + + mGroupsAll.stream() + .filter(group -> !group.mFound) + .filter( + group -> + group.matches(eventText) + || group.matches(viewNodeText) + || group.matches(hintText)) + .findFirst() + .ifPresent( + group -> { + group.mFound = true; + mAnyGroupFound = true; + }); + + boolean loginDetected = + mGroupsRequired.stream().allMatch(group -> group.mFound) + && mGroupsOptional.stream().filter(group -> group.mFound).count() + >= mOptions.optionalGroupsThreshold; + + if (loginDetected) { loginDetected(); } else { maybeResetLoginFlags(); @@ -150,14 +165,13 @@ public class ContentProtectionEventProcessor { @UiThread private void resetLoginFlags() { - mPasswordFieldDetected = false; - mSuspiciousTextDetected = false; - mResetLoginRemainingEventsToProcess = 0; + mGroupsAll.forEach(group -> group.mFound = false); + mAnyGroupFound = false; } @UiThread private void maybeResetLoginFlags() { - if (mPasswordFieldDetected || mSuspiciousTextDetected) { + if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; } else { @@ -194,61 +208,21 @@ public class ContentProtectionEventProcessor { } } - private boolean isPasswordField(@NonNull ContentCaptureEvent event) { - return isPasswordField(event.getViewNode()); - } - - private boolean isPasswordField(@Nullable ViewNode viewNode) { - if (viewNode == null) { - return false; - } - return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode); - } - - private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) { - if (!isAndroidViewNode(viewNode)) { - return false; - } - int inputType = viewNode.getInputType(); - return PASSWORD_FIELD_INPUT_TYPES.stream() - .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0); - } + private static final class SearchGroup { - private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) { - if (viewNode.getClassName() != null) { - return false; - } - return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode)); - } + @NonNull private final List<String> mSearchStrings; - private boolean isAndroidViewNode(@NonNull ViewNode viewNode) { - String className = viewNode.getClassName(); - return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX); - } - - private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) { - return isSuspiciousText(ContentProtectionUtils.getEventText(event)) - || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event)); - } + public boolean mFound = false; - private boolean isSuspiciousText(@Nullable String text) { - if (text == null) { - return false; + SearchGroup(@NonNull List<String> searchStrings) { + mSearchStrings = searchStrings; } - if (isPasswordText(text)) { - return true; - } - String lowerCaseText = text.toLowerCase(); - return ADDITIONAL_SUSPICIOUS_TEXTS.stream() - .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText)); - } - private boolean isPasswordText(@Nullable String text) { - if (text == null) { - return false; + public boolean matches(@Nullable String text) { + if (text == null) { + return false; + } + return mSearchStrings.stream().anyMatch(text::contains); } - String lowerCaseText = text.toLowerCase(); - return PASSWORD_TEXTS.stream() - .anyMatch(passwordText -> lowerCaseText.contains(passwordText)); } } diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java index 9abf6f10d05d..1ecac7f188ab 100644 --- a/core/java/android/view/contentprotection/ContentProtectionUtils.java +++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java @@ -28,33 +28,39 @@ import android.view.contentcapture.ViewNode; */ public final class ContentProtectionUtils { - /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */ + /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */ @Nullable - public static String getEventText(@NonNull ContentCaptureEvent event) { + public static String getEventTextLower(@NonNull ContentCaptureEvent event) { CharSequence text = event.getText(); if (text == null) { return null; } - return text.toString(); + return text.toString().toLowerCase(); } - /** Returns the text extracted from the event's {@link ViewNode}, if set. */ + /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ContentCaptureEvent event) { - ViewNode viewNode = event.getViewNode(); + public static String getViewNodeTextLower(@Nullable ViewNode viewNode) { if (viewNode == null) { return null; } - return getViewNodeText(viewNode); + CharSequence text = viewNode.getText(); + if (text == null) { + return null; + } + return text.toString().toLowerCase(); } - /** Returns the text extracted directly from the {@link ViewNode}, if set. */ + /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ViewNode viewNode) { - CharSequence text = viewNode.getText(); + public static String getHintTextLower(@Nullable ViewNode viewNode) { + if (viewNode == null) { + return null; + } + String text = viewNode.getHint(); if (text == null) { return null; } - return text.toString(); + return text.toLowerCase(); } } 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 7e06f87d931a..5c86feb3c22c 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 6e3d9a8786af..927874ffc3a2 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -106,7 +106,7 @@ public interface DisplayHashResultCallback { * {@link android.view.View#generateDisplayHash(String, Rect, Executor, * DisplayHashResultCallback)} results in an error and cannot generate a display hash. * - * @param errorCode One of the values in {@link DisplayHashErrorCode} + * @param errorCode the error code */ void onDisplayHashError(@DisplayHashErrorCode int errorCode); } diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 56b5fac1c64a..fd9689013af3 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -26,4 +26,11 @@ flag { namespace: "core_graphics" description: "Enable the `setFrameRate` callback" bug: "299946220" +} + +flag { + name: "wm_display_refresh_rate_test" + namespace: "core_graphics" + description: "Adds WindowManager display refresh rate fields to test API" + bug: "304475199" }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index a92420a2f373..c5114b9550db 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -47,7 +47,6 @@ import android.view.MotionEvent; import android.view.MotionEvent.ToolType; import android.view.View; import android.view.autofill.AutofillId; -import android.widget.Editor; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodDebug; @@ -722,9 +721,9 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; /** - * Set {@code true} if the {@link Editor} has + * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. - * {@code false} by default, {@link Editor} must set it {@code true} to indicate that + * {@code false} by default, {@code Editor} must set it {@code true} to indicate that * it supports stylus handwriting. * * @param enabled {@code true} if stylus handwriting is enabled. @@ -736,7 +735,7 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Returns {@code true} when an {@link Editor} has stylus handwriting enabled. + * Returns {@code true} when an {@code Editor} has stylus handwriting enabled. * {@code false} by default. * @see #setStylusHandwritingEnabled(boolean) * @see InputMethodManager#isStylusHandwritingAvailable() diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5bb1e9318175..eeab005771f5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -39,6 +39,7 @@ import android.Manifest; import android.annotation.DisplayContext; import android.annotation.DrawableRes; import android.annotation.DurationMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -100,7 +101,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; @@ -1513,6 +1513,7 @@ public final class InputMethodManager { * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled. * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be * called and Stylus touch should continue as normal touch input. + * * @see #startStylusHandwriting(View) */ public boolean isStylusHandwritingAvailable() { @@ -1536,6 +1537,7 @@ public final class InputMethodManager { @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) { final Context fallbackContext = ActivityThread.currentApplication(); @@ -1656,6 +1658,7 @@ public final class InputMethodManager { @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) { return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier()); @@ -1691,12 +1694,13 @@ public final class InputMethodManager { * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is * different from the calling process user ID. * @return {@link List} of {@link InputMethodSubtype}. - * @see #getEnabledInputMethodListAsUser(int) + * @see #getEnabledInputMethodListAsUser(UserHandle) * @hide */ @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, @@ -2374,16 +2378,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 +2406,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/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index c14b5104242a..1e8718ce42c0 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -14,4 +14,12 @@ flag { description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled" bug: "293898187" is_fixed_read_only: true +} + +flag { + name: "imm_userhandle_hostsidetests" + namespace: "input_method" + description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions" + bug: "301713309" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java index 5be0e3f3ccf2..78de7e16edd7 100644 --- a/core/java/android/view/inspector/PropertyReader.java +++ b/core/java/android/view/inspector/PropertyReader.java @@ -124,7 +124,7 @@ public interface PropertyReader { void readObject(int id, @Nullable Object value); /** - * Read a color packed into a {@link ColorInt} as a property. + * Read a color packed into an int as a property. * * @param id Identifier of the property from a {@link PropertyMapper} * @param value Value of the property diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index f53737abf707..55623608b025 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -49,7 +49,7 @@ public final class BackEvent { private final int mSwipeEdge; /** - * Creates a new {@link BackMotionEvent} instance. + * Creates a new {@link BackEvent} instance. * * @param touchX Absolute X location of the touch point of this event. * @param touchY Absolute Y location of the touch point of this event. diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index 07ac2922304e..2736b68845a2 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -211,7 +211,11 @@ public class SystemPerformanceHinter { session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory( + displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false); transactionChanged = true; Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason, session.traceCookie); @@ -251,7 +255,11 @@ public class SystemPerformanceHinter { session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_SELF); - mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT, + /* smoothSwitchOnly= */ false); transactionChanged = true; Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason, session.traceCookie); diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 43fa0be6c1b7..4e0f9a51c0a0 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -88,6 +88,26 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; + /** + * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the + * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12; + + /** + * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the + * TaskFragment to the top of the Task above all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -101,7 +121,9 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ANIMATION_PARAMS, OP_TYPE_SET_RELATIVE_BOUNDS, OP_TYPE_REORDER_TO_FRONT, - OP_TYPE_SET_ISOLATED_NAVIGATION + OP_TYPE_SET_ISOLATED_NAVIGATION, + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, + OP_TYPE_REORDER_TO_TOP_OF_TASK, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..1a2d202be934 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable { } /** + * Whether this transition contains any changes to the window hierarchy, + * including keyguard visibility. + */ + public boolean hasChangesOrSideEffects() { + return !mChanges.isEmpty() || isKeyguardGoingAway() + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + } + + /** * Whether this transition includes keyguard going away. */ public boolean isKeyguardGoingAway() { @@ -392,6 +402,18 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { + return toString(""); + } + + /** + * Returns a string representation of this transition info. + * @hide + */ + public String toString(@NonNull String prefix) { + final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty(); + final String innerPrefix = shouldPrettyPrint ? prefix + " " : ""; + final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : ""; + final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : ""; StringBuilder sb = new StringBuilder(); sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType)) .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack) @@ -403,12 +425,15 @@ public final class TransitionInfo implements Parcelable { sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset); } sb.append("] c=["); + sb.append(perChangeLineStart); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); + sb.append(perChangeLineStart); } sb.append(mChanges.get(i)); } + sb.append(changesLineStart); sb.append("]}"); return sb.toString(); } diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index 932608a3b57b..bd54e14bc996 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -62,13 +62,16 @@ public final class TransitionRequestInfo implements Parcelable { /** The transition flags known at the time of the request. These may not be complete. */ private final int mFlags; + /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ + private final int mDebugId; + /** constructor override */ public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition) { this(type, triggerTask, null /* pipTask */, - remoteTransition, null /* displayChange */, 0 /* flags */); + remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */); } /** constructor override */ @@ -78,16 +81,29 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable RemoteTransition remoteTransition, int flags) { this(type, triggerTask, null /* pipTask */, - remoteTransition, null /* displayChange */, flags); + remoteTransition, null /* displayChange */, flags, -1 /* debugId */); } + /** constructor override */ public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags) { - this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags); + this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags, + -1 /* debugId */); + } + + /** constructor override */ + public TransitionRequestInfo( + @WindowManager.TransitionType int type, + @Nullable ActivityManager.RunningTaskInfo triggerTask, + @Nullable ActivityManager.RunningTaskInfo pipTask, + @Nullable RemoteTransition remoteTransition, + @Nullable TransitionRequestInfo.DisplayChange displayChange, + int flags) { + this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */); } /** @hide */ @@ -270,7 +286,7 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1695667226050L, + time = 1697564781403L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -318,6 +334,8 @@ public final class TransitionRequestInfo implements Parcelable { * (if size is changing). * @param flags * The transition flags known at the time of the request. These may not be complete. + * @param debugId + * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ @DataClass.Generated.Member public TransitionRequestInfo( @@ -326,7 +344,8 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable ActivityManager.RunningTaskInfo pipTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, - int flags) { + int flags, + int debugId) { this.mType = type; com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); @@ -335,6 +354,7 @@ public final class TransitionRequestInfo implements Parcelable { this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; + this.mDebugId = debugId; // onConstructed(); // You can define this method to get a callback } @@ -392,6 +412,14 @@ public final class TransitionRequestInfo implements Parcelable { } /** + * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! + */ + @DataClass.Generated.Member + public int getDebugId() { + return mDebugId; + } + + /** * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. */ @@ -443,7 +471,8 @@ public final class TransitionRequestInfo implements Parcelable { "pipTask = " + mPipTask + ", " + "remoteTransition = " + mRemoteTransition + ", " + "displayChange = " + mDisplayChange + ", " + - "flags = " + mFlags + + "flags = " + mFlags + ", " + + "debugId = " + mDebugId + " }"; } @@ -465,6 +494,7 @@ public final class TransitionRequestInfo implements Parcelable { if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags); if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags); dest.writeInt(mFlags); + dest.writeInt(mDebugId); } @Override @@ -485,6 +515,7 @@ public final class TransitionRequestInfo implements Parcelable { RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR); int flags = in.readInt(); + int debugId = in.readInt(); this.mType = type; com.android.internal.util.AnnotationValidations.validate( @@ -494,6 +525,7 @@ public final class TransitionRequestInfo implements Parcelable { this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; + this.mDebugId = debugId; // onConstructed(); // You can define this method to get a callback } @@ -513,10 +545,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1695667226088L, + time = 1697564781438L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index 611da3cec5c6..0cbfcc501918 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -155,7 +155,7 @@ public abstract class WindowProviderService extends Service implements WindowPro } /** - * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for + * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for * low memory and trim memory events. */ @Override diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7c931cd9fa15..6c025a47ff5a 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -13,3 +13,18 @@ flag { description: "On close to square display, when necessary, configuration includes status bar" bug: "291870756" } + +flag { + name: "dimmer_refactor" + namespace: "windowing_frontend" + description: "Refactor dim to fix flickers" + bug: "281632483,295291019" + is_fixed_read_only: true +} + +flag { + name: "transit_ready_tracking" + namespace: "windowing_frontend" + description: "Enable accurate transition readiness tracking" + bug: "294925498" +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ec5d4ff16ee5..24dc6dbfede8 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 58376a77c705..0801dd8c0bd8 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/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index 72a1bac28c9f..ca6c54dc0285 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.ActivityThread; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; @@ -147,7 +148,8 @@ public class DisplayResolutionTracker { public void registerDisplayListener(DisplayManager.DisplayListener listener) { manager.registerDisplayListener(listener, handler, DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, + ActivityThread.currentPackageName()); } @Override diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index d3103f1ed24b..039943098d7a 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -37,7 +37,6 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; -import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -78,7 +77,7 @@ public class BatteryStatsHistory { private static final String TAG = "BatteryStatsHistory"; // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - private static final int VERSION = 209; + private static final int VERSION = 210; private static final String HISTORY_DIR = "battery-history"; private static final String FILE_SUFFIX = ".bh"; @@ -194,10 +193,11 @@ public class BatteryStatsHistory { private int mNextHistoryTagIdx = 0; private int mNumHistoryTagChars = 0; private int mHistoryBufferLastPos = -1; - private long mLastHistoryElapsedRealtimeMs = 0; private long mTrackRunningHistoryElapsedRealtimeMs = 0; private long mTrackRunningHistoryUptimeMs = 0; - private long mHistoryBaseTimeMs; + private final MonotonicClock mMonotonicClock; + // Monotonic time when we started writing to the history buffer + private long mHistoryBufferStartTime; private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>(); private byte mLastHistoryStepLevel = 0; private boolean mMutable = true; @@ -307,23 +307,26 @@ public class BatteryStatsHistory { * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps */ public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock) { this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize, - stepDetailsCalculator, clock, new TraceDelegate()); + stepDetailsCalculator, clock, monotonicClock, new TraceDelegate()); initHistoryBuffer(); } @VisibleForTesting public BatteryStatsHistory(Parcel historyBuffer, File systemDir, int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) { + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock, TraceDelegate tracer) { this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, - clock, tracer, null); + clock, monotonicClock, tracer, null); } private BatteryStatsHistory(Parcel historyBuffer, File systemDir, int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer, + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock, TraceDelegate tracer, BatteryStatsHistory writableHistory) { mHistoryBuffer = historyBuffer; mSystemDir = systemDir; @@ -332,6 +335,7 @@ public class BatteryStatsHistory { mStepDetailsCalculator = stepDetailsCalculator; mTracer = tracer; mClock = clock; + mMonotonicClock = monotonicClock; mWritableHistory = writableHistory; if (mWritableHistory != null) { mMutable = false; @@ -381,16 +385,18 @@ public class BatteryStatsHistory { } private BatteryHistoryFile makeBatteryHistoryFile() { - return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs); + return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime()); } public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, - HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock) { mMaxHistoryFiles = maxHistoryFiles; mMaxHistoryBufferSize = maxHistoryBufferSize; mStepDetailsCalculator = stepDetailsCalculator; mTracer = new TraceDelegate(); mClock = clock; + mMonotonicClock = monotonicClock; mHistoryBuffer = Parcel.obtain(); mSystemDir = null; @@ -417,16 +423,16 @@ public class BatteryStatsHistory { mHistoryBuffer = Parcel.obtain(); mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length); + mMonotonicClock = null; readFromParcel(parcel, true /* useBlobs */); } private void initHistoryBuffer() { - mHistoryBaseTimeMs = 0; - mLastHistoryElapsedRealtimeMs = 0; mTrackRunningHistoryElapsedRealtimeMs = 0; mTrackRunningHistoryUptimeMs = 0; mWrittenPowerStatsDescriptors.clear(); + mHistoryBufferStartTime = mMonotonicClock.monotonicTime(); mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); @@ -466,7 +472,7 @@ public class BatteryStatsHistory { historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize()); return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null, - this); + null, this); } } @@ -491,7 +497,7 @@ public class BatteryStatsHistory { * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER}, * create next history file. */ - public void startNextFile() { + public void startNextFile(long elapsedRealtimeMs) { if (mMaxHistoryFiles == 0) { Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history"); return; @@ -517,6 +523,7 @@ public class BatteryStatsHistory { Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile()); } + mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs); mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); @@ -699,9 +706,12 @@ public class BatteryStatsHistory { if (mHistoryParcels != null) { while (mParcelIndex < mHistoryParcels.size()) { final Parcel p = mHistoryParcels.get(mParcelIndex++); - if (!skipHead(p)) { + if (!verifyVersion(p)) { continue; } + // skip monotonic time field. + p.readLong(); + final int bufSize = p.readInt(); final int curPos = p.dataPosition(); mCurrentParcelEnd = curPos + bufSize; @@ -745,24 +755,36 @@ public class BatteryStatsHistory { } out.unmarshall(raw, 0, raw.length); out.setDataPosition(0); - return skipHead(out); + if (!verifyVersion(out)) { + return false; + } + // skip monotonic time field. + out.readLong(); + return true; } /** - * Skip the header part of history parcel. + * Verify header part of history parcel. * - * @param p history parcel to skip head. * @return true if version match, false if not. */ - private boolean skipHead(Parcel p) { + private boolean verifyVersion(Parcel p) { p.setDataPosition(0); final int version = p.readInt(); - if (version != VERSION) { - return false; - } - // skip historyBaseTime field. - p.readLong(); - return true; + return version == VERSION; + } + + /** + * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history + * buffer. + */ + public long getHistoryBufferStartTime(Parcel p) { + int pos = p.dataPosition(); + p.setDataPosition(0); + p.readInt(); // Skip the version field + long monotonicTime = p.readLong(); + p.setDataPosition(pos); + return monotonicTime; } /** @@ -1438,7 +1460,8 @@ public class BatteryStatsHistory { throw new ConcurrentModificationException("Battery history is not writable"); } - final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time; + final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs) + - mHistoryLastWritten.time; final int diffStates = mHistoryLastWritten.states ^ cur.states; final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2; final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states; @@ -1476,7 +1499,9 @@ public class BatteryStatsHistory { mHistoryBuffer.setDataSize(mHistoryBufferLastPos); mHistoryBuffer.setDataPosition(mHistoryBufferLastPos); mHistoryBufferLastPos = -1; - elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs; + + elapsedRealtimeMs -= timeDiffMs; + // If the last written history had a wakelock tag, we need to retain it. // Note that the condition above made sure that we aren't in a case where // both it and the current history item have a wakelock tag. @@ -1513,7 +1538,7 @@ public class BatteryStatsHistory { HistoryItem copy = new HistoryItem(); copy.setTo(cur); - startNextFile(); + startNextFile(elapsedRealtimeMs); // startRecordingHistory will reset mHistoryCur. startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); @@ -1548,7 +1573,7 @@ public class BatteryStatsHistory { mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); mHistoryLastLastWritten.setTo(mHistoryLastWritten); final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence; - mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur); + mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur); if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) { Slog.wtf(TAG, "Significantly earlier event written to battery history:" + " time=" + mHistoryLastWritten.time @@ -1556,7 +1581,6 @@ public class BatteryStatsHistory { } mHistoryLastWritten.tagsFirstOccurrence = hasTags; writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); - mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; cur.wakelockTag = null; cur.wakeReasonTag = null; cur.eventCode = HistoryItem.EVENT_NONE; @@ -1926,6 +1950,10 @@ public class BatteryStatsHistory { return; } + // Save the monotonic time first, so that even if the history write below fails, + // we still wouldn't end up with overlapping history timelines. + mMonotonicClock.write(); + Parcel p = Parcel.obtain(); try { final long start = SystemClock.uptimeMillis(); @@ -1951,8 +1979,7 @@ public class BatteryStatsHistory { return; } - final long historyBaseTime = in.readLong(); - + mHistoryBufferStartTime = in.readLong(); mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); @@ -1972,39 +1999,11 @@ public class BatteryStatsHistory { mHistoryBuffer.appendFrom(in, curPos, bufSize); in.setDataPosition(curPos + bufSize); } - - mHistoryBaseTimeMs = historyBaseTime; - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** NEW mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } - - if (mHistoryBaseTimeMs > 0) { - long elapsedRealtimeMs = mClock.elapsedRealtime(); - mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; - mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1; - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** ADJUSTED mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - Slog.i(TAG, sb.toString()); - } - } } private void writeHistoryBuffer(Parcel out) { - if (DEBUG) { - StringBuilder sb = new StringBuilder(128); - sb.append("****************** WRITING mHistoryBaseTimeMs: "); - TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); - sb.append(" mLastHistoryElapsedRealtimeMs: "); - TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb); - Slog.i(TAG, sb.toString()); - } out.writeInt(BatteryStatsHistory.VERSION); - out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs); + out.writeLong(mHistoryBufferStartTime); out.writeInt(mHistoryBuffer.dataSize()); if (DEBUG) { Slog.i(TAG, "***************** WRITING HISTORY: " diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index a5d2d0fc1a01..6bd5898b1637 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -16,7 +16,6 @@ package com.android.internal.os; -import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; import android.os.BatteryManager; import android.os.BatteryStats; @@ -34,8 +33,8 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistoryItr"; private final BatteryStatsHistory mBatteryStatsHistory; - private final @CurrentTimeMillisLong long mStartTimeMs; - private final @CurrentTimeMillisLong long mEndTimeMs; + private final long mStartTimeMs; + private final long mEndTimeMs; private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails = new BatteryStats.HistoryStepDetails(); private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>(); @@ -43,10 +42,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor new PowerStats.DescriptorRegistry(); private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem(); private boolean mNextItemReady; + private boolean mTimeInitialized; - public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, - @CurrentTimeMillisLong long startTimeMs, - @CurrentTimeMillisLong long endTimeMs) { + public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs, + long endTimeMs) { mBatteryStatsHistory = history; mStartTimeMs = startTimeMs; mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE; @@ -82,7 +81,12 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor break; } - final long lastRealtimeMs = mHistoryItem.time; + if (!mTimeInitialized) { + mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p); + mTimeInitialized = true; + } + + final long lastMonotonicTimeMs = mHistoryItem.time; final long lastWalltimeMs = mHistoryItem.currentTime; try { readHistoryDelta(p, mHistoryItem); @@ -93,12 +97,13 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) { - mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs); + mHistoryItem.currentTime = + lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs); } - if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) { + if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) { break; } - if (mHistoryItem.currentTime >= mStartTimeMs) { + if (mHistoryItem.time >= mStartTimeMs) { mNextItemReady = true; return; } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 5ea6ba86da71..1f44b338f3f7 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -191,6 +191,27 @@ public final class LongArrayMultiStateCounter implements Parcelable { } /** + * Sets the new values for the given state. + */ + public void setValues(int state, long[] values) { + if (state < 0 || state >= mStateCount) { + throw new IllegalArgumentException( + "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]"); + } + if (values.length != mLength) { + throw new IllegalArgumentException( + "Invalid array length: " + values.length + ", expected: " + mLength); + } + LongArrayContainer container = sTmpArrayContainer.getAndSet(null); + if (container == null || container.mLength != values.length) { + container = new LongArrayContainer(values.length); + } + container.setValues(values); + native_setValues(mNativeObject, state, container.mNativeObject); + sTmpArrayContainer.set(container); + } + + /** * Sets the new values. The delta between the previously set values and these values * is distributed among the state according to the time the object spent in those states * since the previous call to updateValues. @@ -317,6 +338,10 @@ public final class LongArrayMultiStateCounter implements Parcelable { private static native void native_setState(long nativeObject, int state, long timestampMs); @CriticalNative + private static native void native_setValues(long nativeObject, int state, + long longArrayContainerNativeObject); + + @CriticalNative private static native void native_updateValues(long nativeObject, long longArrayContainerNativeObject, long timestampMs); diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java new file mode 100644 index 000000000000..6f114e34b21c --- /dev/null +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -0,0 +1,145 @@ +/* + * 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.internal.os; + +import android.annotation.NonNull; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset + * on reboot, but keeps going. + */ +public class MonotonicClock { + private static final String TAG = "MonotonicClock"; + + private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time"; + private static final String XML_ATTR_TIMESHIFT = "timeshift"; + + private final AtomicFile mFile; + private final Clock mClock; + private long mTimeshift; + + public MonotonicClock(File file) { + mFile = new AtomicFile(file); + mClock = Clock.SYSTEM_CLOCK; + read(); + } + + public MonotonicClock(long monotonicTime, @NonNull Clock clock) { + mClock = clock; + mFile = null; + mTimeshift = monotonicTime - mClock.elapsedRealtime(); + } + + /** + * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that + * after a device reboot the time keeps increasing. + */ + public long monotonicTime() { + return monotonicTime(mClock.elapsedRealtime()); + } + + /** + * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead + * of being read from the Clock. + */ + public long monotonicTime(long elapsedRealtimeMs) { + return mTimeshift + elapsedRealtimeMs; + } + + private void read() { + if (!mFile.exists()) { + return; + } + + try { + readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); + } catch (IOException e) { + Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e); + } + } + + /** + * Saves the timeshift into a file. Call this method just before system shutdown, after + * writing the last battery history event. + */ + public void write() { + if (mFile == null) { + return; + } + + mFile.write(out -> { + try { + writeXml(out, Xml.newBinarySerializer()); + out.close(); + } catch (IOException e) { + Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); + } + }); + } + + /** + * Parses an XML file containing the persistent state of the monotonic clock. + */ + @VisibleForTesting + public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { + long savedTimeshift = 0; + try { + parser.setInput(inputStream, StandardCharsets.UTF_8.name()); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG + && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) { + savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT); + } + eventType = parser.next(); + } + } catch (XmlPullParserException e) { + throw new IOException(e); + } + mTimeshift = savedTimeshift - mClock.elapsedRealtime(); + } + + /** + * Creates an XML file containing the persistent state of the monotonic clock. + */ + @VisibleForTesting + public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_MONOTONIC_TIME); + serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime()); + serializer.endTag(null, XML_TAG_MONOTONIC_TIME); + serializer.endDocument(); + } +} diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java index f971849987dd..dc5055a0881b 100644 --- a/core/java/com/android/internal/os/MultiStateStats.java +++ b/core/java/com/android/internal/os/MultiStateStats.java @@ -16,9 +16,17 @@ package com.android.internal.os; +import android.util.Slog; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; @@ -29,15 +37,21 @@ import java.util.Arrays; * values; */ public class MultiStateStats { + private static final String TAG = "MultiStateStats"; + + private static final String XML_TAG_STATS = "stats"; + /** * A set of states, e.g. on-battery, screen-on, procstate. The state values are integers * from 0 to States.mLabels.length */ public static class States { + final String mName; final boolean mTracked; final String[] mLabels; - public States(boolean tracked, String... labels) { + public States(String name, boolean tracked, String... labels) { + mName = name; this.mTracked = tracked; this.mLabels = labels; } @@ -155,11 +169,28 @@ public class MultiStateStats { >>> mStateBitFieldShifts[stateIndex]; } - private int setStateInComposite(int baseCompositeState, int stateIndex, int value) { + int setStateInComposite(int baseCompositeState, int stateIndex, int value) { return (baseCompositeState & ~mStateBitFieldMasks[stateIndex]) | (value << mStateBitFieldShifts[stateIndex]); } + int setStateInComposite(int compositeState, String stateName, String stateLabel) { + for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) { + States stateConfig = mStates[stateIndex]; + if (stateConfig.mName.equals(stateName)) { + for (int state = 0; state < stateConfig.mLabels.length; state++) { + if (stateConfig.mLabels[state].equals(stateLabel)) { + return setStateInComposite(compositeState, stateIndex, state); + } + } + Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName); + return -1; + } + } + Slog.e(TAG, "Unsupported state: " + stateName); + return -1; + } + /** * Allocates a new stats container using this Factory's configuration. */ @@ -195,6 +226,10 @@ public class MultiStateStats { } return serialState; } + + int getSerialState(int compositeState) { + return mCompositeToSerialState[compositeState]; + } } private final Factory mFactory; @@ -254,6 +289,106 @@ public class MultiStateStats { } /** + * Stores contents in an XML doc. + */ + public void writeXml(TypedXmlSerializer serializer) throws IOException { + long[] tmpArray = new long[mCounter.getArrayLength()]; + writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray); + } + + private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex, + long[] values) throws IOException { + if (stateIndex < states.length) { + if (!mFactory.mStates[stateIndex].mTracked) { + writeXmlAllStates(serializer, states, stateIndex + 1, values); + return; + } + + for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) { + states[stateIndex] = i; + writeXmlAllStates(serializer, states, stateIndex + 1, values); + } + return; + } + + mCounter.getCounts(values, mFactory.getSerialState(states)); + boolean nonZero = false; + for (long value : values) { + if (value != 0) { + nonZero = true; + break; + } + } + if (!nonZero) { + return; + } + + serializer.startTag(null, XML_TAG_STATS); + + for (int i = 0; i < states.length; i++) { + if (mFactory.mStates[i].mTracked && states[i] != 0) { + serializer.attribute(null, mFactory.mStates[i].mName, + mFactory.mStates[i].mLabels[states[i]]); + } + } + for (int i = 0; i < values.length; i++) { + if (values[i] != 0) { + serializer.attributeLong(null, "_" + i, values[i]); + } + } + serializer.endTag(null, XML_TAG_STATS); + } + + /** + * Populates the object with contents in an XML doc. The parser is expected to be + * positioned on the opening tag of the corresponding element. + */ + public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, + IOException { + String outerTag = parser.getName(); + long[] tmpArray = new long[mCounter.getArrayLength()]; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(XML_TAG_STATS)) { + Arrays.fill(tmpArray, 0); + int compositeState = 0; + int attributeCount = parser.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + String attributeName = parser.getAttributeName(i); + if (attributeName.startsWith("_")) { + int index; + try { + index = Integer.parseInt(attributeName.substring(1)); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Unexpected index syntax: " + attributeName, parser, e); + } + if (index < 0 || index >= tmpArray.length) { + Slog.e(TAG, "State index out of bounds: " + index + + " length: " + tmpArray.length); + return false; + } + tmpArray[index] = parser.getAttributeLong(i); + } else { + String attributeValue = parser.getAttributeValue(i); + compositeState = mFactory.setStateInComposite(compositeState, + attributeName, attributeValue); + if (compositeState == -1) { + return false; + } + } + } + mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray); + } + } + eventType = parser.next(); + } + return true; + } + + /** * Prints the accumulated stats, one line of every combination of states that has data. */ public void dump(PrintWriter pw) { diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index e35b7f184663..996e4244c473 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -5,14 +5,10 @@ per-file *Binder* = file:BINDER_OWNERS per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS # BatteryStats -per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS per-file BatteryStats* = file:/BATTERY_STATS_OWNERS -per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS -per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS -per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS -per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS -per-file *PowerStats* = file:/BATTERY_STATS_OWNERS per-file *Kernel* = file:/BATTERY_STATS_OWNERS +per-file *Clock* = file:/BATTERY_STATS_OWNERS per-file *MultiState* = file:/BATTERY_STATS_OWNERS per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS +per-file *PowerStats* = file:/BATTERY_STATS_OWNERS diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 8f66d1f9365c..1130a454c6b0 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -24,9 +24,16 @@ import android.os.Parcel; import android.os.PersistableBundle; import android.os.UserHandle; import android.util.IndentingPrintWriter; -import android.util.Log; +import android.util.Slog; import android.util.SparseArray; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.util.Arrays; import java.util.Objects; @@ -61,6 +68,13 @@ public final class PowerStats { * to adjust the algorithm in accordance with the stats available on the device. */ public static class Descriptor { + public static final String XML_TAG_DESCRIPTOR = "descriptor"; + private static final String XML_ATTR_ID = "id"; + private static final String XML_ATTR_NAME = "name"; + private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length"; + private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length"; + private static final String XML_TAG_EXTRAS = "extras"; + /** * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates * to; or a custom power component ID (if the value @@ -126,7 +140,7 @@ public final class PowerStats { int firstWord = parcel.readInt(); int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT; if (version != PARCEL_FORMAT_VERSION) { - Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has " + Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has " + "changed from " + version + " to " + PARCEL_FORMAT_VERSION); return null; } @@ -155,6 +169,71 @@ public final class PowerStats { that.extras); // Since the Parcel is now unparceled, do a deep comparison } + /** + * Stores contents in an XML doc. + */ + public void writeXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_DESCRIPTOR); + serializer.attributeInt(null, XML_ATTR_ID, powerComponentId); + serializer.attribute(null, XML_ATTR_NAME, name); + serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength); + serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength); + try { + serializer.startTag(null, XML_TAG_EXTRAS); + extras.saveToXml(serializer); + serializer.endTag(null, XML_TAG_EXTRAS); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + serializer.endTag(null, XML_TAG_DESCRIPTOR); + } + + /** + * Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned + * on or before the opening "descriptor" tag. + */ + public static Descriptor createFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + int powerComponentId = -1; + String name = null; + int statsArrayLength = 0; + int uidStatsArrayLength = 0; + PersistableBundle extras = null; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG + && parser.getName().equals(XML_TAG_DESCRIPTOR))) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case XML_TAG_DESCRIPTOR: + powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID); + name = parser.getAttributeValue(null, XML_ATTR_NAME); + statsArrayLength = parser.getAttributeInt(null, + XML_ATTR_STATS_ARRAY_LENGTH); + uidStatsArrayLength = parser.getAttributeInt(null, + XML_ATTR_UID_STATS_ARRAY_LENGTH); + break; + case XML_TAG_EXTRAS: + extras = PersistableBundle.restoreFromXml(parser); + break; + } + } + eventType = parser.next(); + } + if (powerComponentId == -1) { + return null; + } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { + return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength, + extras); + } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) { + return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength, + extras); + } else { + Slog.e(TAG, "Unrecognized power component: " + powerComponentId); + return null; + } + } + @Override public int hashCode() { return Objects.hash(powerComponentId); @@ -259,7 +338,7 @@ public final class PowerStats { Descriptor descriptor = registry.get(powerComponentId); if (descriptor == null) { - Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId); + Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId); return null; } PowerStats stats = new PowerStats(descriptor); @@ -274,7 +353,7 @@ public final class PowerStats { stats.uidStats.put(uid, uidStats); } if (parcel.dataPosition() != endPos) { - Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length + Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length + ", actual length: " + (parcel.dataPosition() - startPos)); return null; } diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java index 6fd50180e78b..504928cd4347 100644 --- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java +++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java @@ -74,16 +74,15 @@ public class WearGestureInterceptionDetector { return windowSwipeToDismiss; } - private boolean isPointerIndexValid(MotionEvent ev) { + private int getIndexForValidPointer(MotionEvent ev) { int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { if (DEBUG) { Log.e(TAG, "Invalid pointer index: ignoring."); } mDiscardIntercept = true; - return false; } - return true; + return pointerIndex; } private void updateSwiping(MotionEvent ev) { @@ -98,7 +97,7 @@ public class WearGestureInterceptionDetector { } } - private void updateDiscardIntercept(MotionEvent ev) { + private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) { if (!mSwiping) { // Don't look at canScroll until we have passed the touch slop return; @@ -107,8 +106,8 @@ public class WearGestureInterceptionDetector { return; } final boolean checkLeft = mDownX < ev.getRawX(); - final float x = ev.getX(mActivePointerId); - final float y = ev.getY(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) { mDiscardIntercept = true; } @@ -152,11 +151,12 @@ public class WearGestureInterceptionDetector { if (mDiscardIntercept) { break; } - if (!isPointerIndexValid(ev)) { + final int pointerIndex = getIndexForValidPointer(ev); + if (pointerIndex == -1) { break; } updateSwiping(ev); - updateDiscardIntercept(ev); + updateDiscardIntercept(ev, pointerIndex); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index ec525f09fa88..4bb7c33b41e2 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -91,6 +91,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, "CoreBackPreview"), WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), + + WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5d70d379e0c..50253cf9e457 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_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index deb138fda867..9c883d18a9ae 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -265,6 +265,18 @@ static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong s return mgr->isDataInjectionEnabled(); } +static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, + jlong sensorManager) { + SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); + return mgr->isReplayDataInjectionEnabled(); +} + +static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, + jlong sensorManager) { + SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); + return mgr->isHalBypassReplayDataInjectionEnabled(); +} + static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, jint deviceId, jlong size, jint channelType, jint fd, jobject hardwareBufferObj) { @@ -533,6 +545,11 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = { {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, + {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled}, + + {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z", + (void *)nativeIsHalBypassReplayDataInjectionEnabled}, + {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I", (void *)nativeCreateDirectChannel}, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ba644eb0c03e..178c0d0d95be 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -963,10 +963,11 @@ static void nativeSetDefaultFrameRateCompatibility(JNIEnv* env, jclass clazz, jl } static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, jint category) { + jlong nativeObject, jint category, + jboolean smoothSwitchOnly) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category)); + transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly); } static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -2181,7 +2182,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetFrameRate }, {"nativeSetDefaultFrameRateCompatibility", "(JJI)V", (void*)nativeSetDefaultFrameRateCompatibility}, - {"nativeSetFrameRateCategory", "(JJI)V", + {"nativeSetFrameRateCategory", "(JJIZ)V", (void*)nativeSetFrameRateCategory}, {"nativeSetFrameRateSelectionStrategy", "(JJI)V", (void*)nativeSetFrameRateSelectionStrategy}, diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 69202111f74c..1f29735b93a4 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -55,6 +55,15 @@ static void native_setState(jlong nativePtr, jint state, jlong timestamp) { counter->setState(state, timestamp); } +static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + std::vector<uint64_t> *vector = + reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); + + counter->setValue(state, *vector); +} + static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr, jlong timestamp) { battery::LongArrayMultiStateCounter *counter = @@ -210,6 +219,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { // @CriticalNative {"native_setState", "(JIJ)V", (void *)native_setState}, // @CriticalNative + {"native_setValues", "(JIJ)V", (void *)native_setValues}, + // @CriticalNative {"native_updateValues", "(JJJ)V", (void *)native_updateValues}, // @CriticalNative {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues}, diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index eb14db070f9f..068f4dd07ccb 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -115,6 +115,9 @@ message PackageProto { // Only set if the app defined a monochrome icon. optional string monochrome_icon_bitmap_path = 3; + + // The component name of the original activity (pre-archival). + optional string original_component_name = 4; } /** Information about main activities. */ diff --git a/core/res/Android.bp b/core/res/Android.bp index b71995f899c5..4e686b7ad80c 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -104,6 +104,7 @@ genrule { android_app { name: "framework-res", + use_resource_processor: false, sdk_version: "core_platform", certificate: "platform", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b0ecc60de597..cffbaa75ab76 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2108,12 +2108,12 @@ android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service interfaces. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.MANAGE_REMOTE_AUTH" android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service authentication methods. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.USE_REMOTE_AUTH" android:protectionLevel="signature" /> @@ -6122,7 +6122,9 @@ android:protectionLevel="signature|privileged|development|appop|retailDemo" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> - <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager --> + <!-- @SystemApi @hide + @FlaggedApi("android.app.usage.report_usage_stats_permission") + Allows trusted system components to report events to UsageStatsManager --> <permission android:name="android.permission.REPORT_USAGE_STATS" android:protectionLevel="signature|module" /> @@ -7235,13 +7237,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 sandboxed {@link HotwordDetectionService}. <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 a sandboxed {@link HotwordDetectionService} or + {@link VisualQueryDetectionService}. + <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/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml new file mode 100644 index 000000000000..dddad814b451 --- /dev/null +++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml @@ -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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="20" + android:viewportHeight="20"> + <path + android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z" + android:fillColor="#FFFFFFFF" + android:fillType="evenOdd"/> +</vector> + + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e54347fc2744..04fd70a96201 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10118,6 +10118,9 @@ screen that can be used to provide more information about a provider. For longer strings it will be truncated. --> <attr name="settingsSubtitle" format="string" /> + <!-- Fully qualified class name of an activity that allows the user to modify + the settings for this service. --> + <attr name="settingsActivity" /> </declare-styleable> <!-- A list of capabilities that indicates to the OS what kinds of credentials diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b211ac2fd316..929133f792c0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3988,6 +3988,13 @@ limit is unknown. --> <item name="config_hapticChannelMaxVibrationAmplitude" format="float" type="dimen">0</item> + <!-- The fixed keyboard vibration strength in [0,1], or -1 to indicate the strength not fixed + and should depend on the touch feedback intensity user setting --> + <item name="config_keyboardHapticFeedbackFixedAmplitude" format="float" type="dimen">-1</item> + + <!-- The default value for keyboard vibration toggle in settings. --> + <bool name="config_defaultKeyboardVibrationEnabled">true</bool> + <!-- If the device should still vibrate even in low power mode, for certain priority vibrations (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. --> <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool> diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index 8fb48bcb8947..e42962ce9195 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -31,4 +31,13 @@ is a relatively expensive operation, this throttle period may need to be adjusted for low-power devices--> <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer> + + <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power + stats aggregation procedure is performed and the results stored in PowerStatsStore. --> + <integer name="config_powerStatsAggregationPeriod">14400000</integer> + + <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery + history time for every aggregated power stats span that is stored stored in PowerStatsStore. + It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) --> + <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index fac6aac1db16..a2a4e34f3527 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -826,6 +826,9 @@ security policy. [CHAR_LIMIT=NONE]--> <string name="notification_channel_accessibility_security_policy">Accessibility usage</string> + <!-- Text shown when viewing channel settings for notifications related to displays --> + <string name="notification_channel_display">Display</string> + <!-- Label for foreground service notification when one app is running. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] --> <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is @@ -6290,6 +6293,16 @@ ul.</string> <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] --> <string name="mic_access_off_toast">Microphone is blocked</string> + <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] --> + <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</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 49a5a7224850..2b9194f8c54e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1997,6 +1997,7 @@ <java-symbol type="drawable" name="stat_sys_throttled" /> <java-symbol type="drawable" name="vpn_connected" /> <java-symbol type="drawable" name="vpn_disconnected" /> + <java-symbol type="drawable" name="usb_cable_unknown_issue" /> <java-symbol type="id" name="ask_checkbox" /> <java-symbol type="id" name="compat_checkbox" /> <java-symbol type="id" name="original_app_icon" /> @@ -2052,6 +2053,8 @@ <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> + <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" /> + <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" /> <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" /> <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" /> <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" /> @@ -3813,6 +3816,7 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="notification_channel_accessibility_magnification" /> <java-symbol type="string" name="notification_channel_accessibility_security_policy" /> + <java-symbol type="string" name="notification_channel_display" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultFieldClassificationService" /> <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" /> @@ -5066,6 +5070,10 @@ <java-symbol type="array" name="device_state_notification_thermal_contents"/> <java-symbol type="array" name="device_state_notification_power_save_titles"/> <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"/> @@ -5118,6 +5126,8 @@ <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" /> <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" /> <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" /> + <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> + <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" /> <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/> <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index af8c69ea1441..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -54,6 +54,9 @@ <!-- Azerbaijan: 4-5 digits, known premium codes listed --> <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" /> + <!-- Bangladesh: 1-5 digits (standard system default, not country specific) --> + <shortcode country="bd" pattern="\\d{1,5}" free="16672" /> + <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp --> <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" /> @@ -145,7 +148,7 @@ <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" /> <!-- Indonesia: 1-5 digits (standard system default, not country specific) --> - <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" /> + <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" /> <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU: http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf --> @@ -190,7 +193,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> @@ -205,7 +208,7 @@ <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" /> <!-- New Zealand: 3-4 digits, known premium codes listed --> - <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> + <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 2993a0e63228..445ddf52bf1c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -65,6 +65,7 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", + "flag-junit", ], libs: [ @@ -75,6 +76,7 @@ android_test { "framework", "ext", "framework-res", + "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", 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 000000000000..c00eb91d752a --- /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/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java index f8348d28b7fe..eefa6e482adf 100644 --- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java +++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java @@ -32,6 +32,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.List; + /** * Unit test for {@link ContentCaptureOptions}. * @@ -44,6 +46,9 @@ public class ContentCaptureOptionsTest { private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo"); private static final ComponentName COMPONENT1 = new ComponentName("comp", "one"); private static final ComponentName COMPONENT2 = new ComponentName("two", "comp"); + private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS = + List.of(List.of("first"), List.of("second", "third"), List.of()); + private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of(); private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS = new ContentCaptureOptions( /* loggingLevel= */ 1000, @@ -55,7 +60,10 @@ public class ContentCaptureOptionsTest { /* enableReceiver= */ false, new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, - /* bufferSize= */ 2001), + /* bufferSize= */ 2001, + CONTENT_PROTECTION_REQUIRED_GROUPS, + CONTENT_PROTECTION_OPTIONAL_GROUPS, + /* optionalGroupsThreshold= */ 2002), /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2)); @Mock private Context mContext; @@ -134,6 +142,19 @@ public class ContentCaptureOptionsTest { .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver) .append(", bufferSize=") .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize) + .append(", requiredGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups + .size()) + .append(", optionalGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups + .size()) + .append(", optionalGroupsThreshold=") + .append( + CONTENT_CAPTURE_OPTIONS + .contentProtectionOptions + .optionalGroupsThreshold) .append("], whitelisted=") .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents) .append(']') @@ -166,6 +187,15 @@ public class ContentCaptureOptionsTest { .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver); assertThat(actual.contentProtectionOptions.bufferSize) .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize); + assertThat(actual.contentProtectionOptions.requiredGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups); + assertThat(actual.contentProtectionOptions.optionalGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups); + assertThat(actual.contentProtectionOptions.optionalGroupsThreshold) + .isEqualTo( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold); assertThat(actual.whitelistedComponents) .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents); } diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 0941a2b6d263..6c14ee382bdc 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest { ); }); - PollingCheck.waitFor(/* timeout= */ 7000, () -> { + PollingCheck.waitFor(/* timeout= */ 10000, () -> { AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false); rule.getScenario().onActivity(activity -> isActivityAtCorrectScale.set( @@ -163,7 +163,7 @@ public class FontScaleConverterActivityTest { }); PollingCheck.waitFor( - /* timeout= */ 5000, + /* timeout= */ 10000, () -> InstrumentationRegistry.getInstrumentation() .getContext() .getResources() diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 149f58f0a69b..c2e6b60cd680 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -42,8 +42,8 @@ import org.mockito.MockitoAnnotations; public class DisplayManagerGlobalTest { private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; @Mock private IDisplayManager mDisplayManager; @@ -69,7 +69,8 @@ public class DisplayManagerGlobalTest { @Test public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException { - mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, + null); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -97,26 +98,27 @@ public class DisplayManagerGlobalTest { public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException { // First we subscribe to all events in order to test that the subsequent calls to // registerDisplayListener will update the event mask. - mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, + null); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); int displayId = 1; mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); @@ -139,7 +141,7 @@ public class DisplayManagerGlobalTest { public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners() throws RemoteException { mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null); InOrder inOrder = Mockito.inOrder(mDisplayManager); inOrder.verify(mDisplayManager) @@ -163,6 +165,7 @@ public class DisplayManagerGlobalTest { } private void waitForHandler() { - mHandler.runWithScissors(() -> { }, 0); + mHandler.runWithScissors(() -> { + }, 0); } } diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 07dec5d9e222..b843ad75ac0f 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -79,6 +79,8 @@ public class FaceManagerTest { private FaceManager.AuthenticationCallback mAuthCallback; @Mock private FaceManager.EnrollmentCallback mEnrollmentCallback; + @Mock + private FaceManager.FaceDetectionCallback mFaceDetectionCallback; @Captor private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor; @@ -191,6 +193,23 @@ public class FaceManagerTest { any(), anyString(), any(), any(), anyBoolean()); } + @Test + public void detectClient_onError() throws RemoteException { + ArgumentCaptor<IFaceServiceReceiver> argumentCaptor = + ArgumentCaptor.forClass(IFaceServiceReceiver.class); + + CancellationSignal cancellationSignal = new CancellationSignal(); + mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback, + new FaceAuthenticateOptions.Builder().build()); + + verify(mService).detectFace(any(), argumentCaptor.capture(), any()); + + argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(mFaceDetectionCallback).onDetectionError(anyInt()); + } + private void initializeProperties() throws RemoteException { verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index 625e2e3723a7..70313b8c9ea7 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -74,6 +74,8 @@ public class FingerprintManagerTest { private FingerprintManager.AuthenticationCallback mAuthCallback; @Mock private FingerprintManager.EnrollmentCallback mEnrollCallback; + @Mock + private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback; @Captor private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor; @@ -166,4 +168,21 @@ public class FingerprintManagerTest { anyString()); verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt()); } + + @Test + public void detectClient_onError() throws RemoteException { + ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor = + ArgumentCaptor.forClass(IFingerprintServiceReceiver.class); + + mFingerprintManager.detectFingerprint(new CancellationSignal(), + mFingerprintDetectionCallback, + new FingerprintAuthenticateOptions.Builder().build()); + + verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any()); + + argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(mFingerprintDetectionCallback).onDetectionError(anyInt()); + } } 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 e1f9523f1b49..000000000000 --- 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/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 6a9fc04230f8..1a38decae604 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -17,6 +17,11 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -48,8 +53,12 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; 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.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; @@ -97,6 +106,10 @@ public class ViewRootImplTest { // state after the test completes. private static boolean sOriginalTouchMode; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); @@ -427,6 +440,129 @@ public class ViewRootImplTest { assertThat(result).isFalse(); } + /** + * Test the default values are properly set + */ + @UiThreadTest + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_getDefaultValues() { + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, + sContext.getDisplayNoVerify()); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); + } + + /** + * Test the value of the frame rate cateogry based on the visibility of a view + * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE + * Visible: FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_visibility() { + View view = new View(sContext); + attachViewToWindow(view); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.INVISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + }); + + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.VISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * <7%: FRAME_RATE_CATEGORY_LOW + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_smallSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + wmlp.width = 1; + wmlp.height = 1; + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * >=7% : FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_normalSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + wmlp.width = (int) (metrics.widthPixels * 0.9); + wmlp.height = (int) (metrics.heightPixels * 0.9); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test how values of the frame rate cateogry are aggregated. + * It should take the max value among all of the voted categories per frame. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.runOnMainSync(() -> { + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + }); + } + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 101f7c21fa19..5c411d5335a7 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -31,6 +31,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; + /** * Unit test for {@link ContentCaptureManager}. * @@ -69,7 +71,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ false, BUFFER_SIZE)); + /* enableReceiver= */ false, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -82,7 +88,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, /* bufferSize= */ 0)); + /* enableReceiver= */ true, + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -95,7 +105,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, BUFFER_SIZE)); + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 3373b8b13273..d47d7891d0e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -47,6 +47,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -112,7 +113,31 @@ public class MainContentCaptureSessionTest { createOptions( /* enableContentCaptureReceiver= */ true, new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, -BUFFER_SIZE)); + /* enableReceiver= */ true, + -BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + MainContentCaptureSession session = createSession(options); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); MainContentCaptureSession session = createSession(options); session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; @@ -313,7 +338,11 @@ public class MainContentCaptureSessionTest { return createOptions( enableContentCaptureReceiver, new ContentCaptureOptions.ContentProtectionOptions( - enableContentProtectionReceiver, BUFFER_SIZE)); + enableContentProtectionReceiver, + BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); } private ContentCaptureManager createManager(ContentCaptureOptions options) { diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index 39a2e0e048e8..ba0dbf454ad2 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -29,12 +29,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentCaptureOptions; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.os.Looper; -import android.text.InputType; +import android.os.test.TestLooper; import android.view.View; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -57,6 +58,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.mockito.verification.VerificationMode; import java.time.Instant; import java.util.ArrayList; @@ -75,13 +77,25 @@ public class ContentProtectionEventProcessorTest { private static final String PACKAGE_NAME = "com.test.package.name"; - private static final String ANDROID_CLASS_NAME = "android.test.some.class.name"; + private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT"; - private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE"; + private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT"; - private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN"; + private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT"; - private static final String SAFE_TEXT = "SAFE TEXT"; + private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT"; + + private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT"; + + private static final String TEXT_SHARED = "TEXT SHARED TEXT"; + + private static final String TEXT_SAFE = "TEXT SAFE TEXT"; + + private static final List<List<String>> REQUIRED_GROUPS = + List.of(List.of("required1", "missing"), List.of("required2", "shared")); + + private static final List<List<String>> OPTIONAL_GROUPS = + List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared")); private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); @@ -91,7 +105,17 @@ public class ContentProtectionEventProcessorTest { private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); - private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; + private static final int BUFFER_SIZE = 150; + + private static final int OPTIONAL_GROUPS_THRESHOLD = 1; + + private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS = + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, + REQUIRED_GROUPS, + OPTIONAL_GROUPS, + OPTIONAL_GROUPS_THRESHOLD); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -101,16 +125,19 @@ public class ContentProtectionEventProcessorTest { private final Context mContext = ApplicationProvider.getApplicationContext(); - private ContentProtectionEventProcessor mContentProtectionEventProcessor; + private final TestLooper mTestLooper = new TestLooper(); + + @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor; @Before public void setup() { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mMockEventBuffer, - new Handler(Looper.getMainLooper()), + new Handler(mTestLooper.getLooper()), mMockContentCaptureManager, - PACKAGE_NAME); + PACKAGE_NAME, + OPTIONS); } @Test @@ -156,347 +183,224 @@ public class ContentProtectionEventProcessorTest { } @Test - public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - - for (int type = -100; type <= 100; type++) { - if (type == TYPE_VIEW_APPEARED) { - continue; - } - - mContentProtectionEventProcessor.processEvent(createEvent(type)); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - } - - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected() throws Exception { + public void processEvent_loginDetected_true_eventText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); - } - - @Test - public void processEvent_loginDetected_passwordFieldNotDetected() { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_suspiciousTextNotDetected() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_withoutViewNode() { - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED1, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED2, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_OPTIONAL1, + /* viewNodeText= */ null, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_belowResetLimit() throws Exception { + public void processEvent_loginDetected_true_viewNodeText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED1, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED2, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_OPTIONAL1, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_aboveResetLimit() throws Exception { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + public void processEvent_loginDetected_true_hintText() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_usesContains() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3)); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, times(2)).clear(); - verify(mMockEventBuffer, times(2)).toArray(); - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); + assertLoginDetected(); } @Test - public void isPasswordField_android() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingRequiredGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_withoutClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingOptionalGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - "wrong.prefix" + ANDROID_CLASS_NAME, - InputType.TYPE_TEXT_VARIATION_PASSWORD); + public void processEvent_loginDetected_false_safeText() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongInputType() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL); + public void processEvent_loginDetected_false_sharedTextOnce() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView() throws Exception { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); - when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); + public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(event, /* times= */ 1); + assertLoginDetected(); } @Test - public void isPasswordField_webView_withClassName() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT); + public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); + for (int type = -100; type <= 100; type++) { + if (type == TYPE_VIEW_APPEARED) { + continue; + } + ContentCaptureEvent event = createEvent(type); + event.setText(TEXT_OPTIONAL1); + mContentProtectionEventProcessor.processEvent(event); + } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView_withSafeViewNodeText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, SAFE_TEXT); + public void processEvent_loginDetected_true_belowResetLimit() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 2; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isPasswordField_webView_withEventText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_withSafeText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT); + public void processEvent_loginDetected_false_aboveResetLimit() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 1; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isSuspiciousText_eventText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isSuspiciousText_viewNodeText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT); + public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + for (int i = 0; i < 2; i++) { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_eventText_passwordText() { - ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - @Test - public void isSuspiciousText_viewNodeText_passwordText() { - // Specify the class to differ from {@link isPasswordField_webView} test in this version - ContentCaptureEvent event = - createProcessEvent( - "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT); + mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(times(2)); } private static ContentCaptureEvent createEvent(int type) { @@ -511,20 +415,20 @@ public class ContentProtectionEventProcessorTest { return createEvent(TYPE_VIEW_APPEARED); } + private ContentCaptureEvent createProcessEvent(@Nullable String eventText) { + return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null); + } + private ContentCaptureEvent createProcessEvent( - @Nullable String className, - int inputType, - @Nullable String eventText, - @Nullable String viewNodeText) { + @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) { View view = new View(mContext); ViewStructureImpl viewStructure = new ViewStructureImpl(view); - if (className != null) { - viewStructure.setClassName(className); - } if (viewNodeText != null) { viewStructure.setText(viewNodeText); } - viewStructure.setInputType(inputType); + if (hintText != null) { + viewStructure.setHint(hintText); + } ContentCaptureEvent event = createProcessEvent(); event.setViewNode(viewStructure.getNode()); @@ -535,34 +439,28 @@ public class ContentProtectionEventProcessorTest { return event; } - private ContentCaptureEvent createAndroidPasswordFieldEvent( - @Nullable String className, int inputType) { - return createProcessEvent( - className, inputType, /* eventText= */ null, /* viewNodeText= */ null); - } - - private ContentCaptureEvent createWebViewPasswordFieldEvent( - @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginNotDetected() { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, never()).clear(); + verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } - private ContentCaptureEvent createSuspiciousTextEvent( - @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent( - /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginDetected() throws Exception { + assertLoginDetected(times(1)); } - private void assertOnLoginDetected() throws Exception { - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); - } + private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, verificationMode).clear(); + verify(mMockEventBuffer, verificationMode).toArray(); - private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception { ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); - verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); + verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture()); assertThat(captor.getValue()).isNotNull(); List<ContentCaptureEvent> actual = captor.getValue().getList(); assertThat(actual).isNotNull(); - assertThat(actual).containsExactly(event); + assertThat(actual).containsExactly(PROCESS_EVENT); } } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java index 1459799adee5..fbe478e31888 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -44,68 +44,74 @@ public class ContentProtectionUtilsTest { private static final String TEXT = "TEST_TEXT"; - private static final ContentCaptureEvent EVENT = createEvent(); - - private static final ViewNode VIEW_NODE = new ViewNode(); - - private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText(); + private static final String TEXT_LOWER = TEXT.toLowerCase(); @Test - public void event_getEventText_null() { - String actual = ContentProtectionUtils.getEventText(EVENT); + public void getEventTextLower_null() { + String actual = ContentProtectionUtils.getEventTextLower(createEvent()); assertThat(actual).isNull(); } @Test - public void event_getEventText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setText(TEXT); - - String actual = ContentProtectionUtils.getEventText(event); + public void getEventTextLower_notNull() { + String actual = ContentProtectionUtils.getEventTextLower(createEventWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void event_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(EVENT); + public void getViewNodeTextLower_null() { + String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void event_getViewNodeText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setViewNode(VIEW_NODE_WITH_TEXT); - - String actual = ContentProtectionUtils.getViewNodeText(event); + public void getViewNodeTextLower_notNull() { + String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void viewNode_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE); + public void getHintTextLower_null() { + String actual = ContentProtectionUtils.getHintTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void viewNode_getViewNodeText_notNull() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT); + public void getHintTextLower_notNull() { + String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } private static ContentCaptureEvent createEvent() { return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED); } - private static ViewNode createViewNodeWithText() { + private static ContentCaptureEvent createEventWithText() { + ContentCaptureEvent event = createEvent(); + event.setText(TEXT); + return event; + } + + private static ViewStructureImpl createViewStructureImpl() { View view = new View(ApplicationProvider.getApplicationContext()); - ViewStructureImpl viewStructure = new ViewStructureImpl(view); - viewStructure.setText(TEXT); - return viewStructure.getNode(); + return new ViewStructureImpl(view); + } + + private static ViewNode createViewNodeWithText() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setText(TEXT); + return viewStructureImpl.getNode(); + } + + private static ViewNode createViewNodeWithHint() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setHint(TEXT); + return viewStructureImpl.getNode(); } } diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index 86f26e59e370..df212ebe1744 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -17,7 +17,9 @@ package android.widget; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.PollingCheck; @@ -32,6 +34,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @MediumTest @Presubmit @@ -49,23 +54,43 @@ public class HorizontalScrollViewFunctionalTest { } @Test - public void testScrollAfterFlingTop() { - mHorizontalScrollView.scrollTo(100, 0); - mHorizontalScrollView.fling(-10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + public void testScrollAfterFlingLeft() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowLeft = edgeEffect; + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame + PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f); assertEquals(0, mHorizontalScrollView.getScrollX()); } @Test - public void testScrollAfterFlingBottom() { + public void testScrollAfterFlingRight() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowRight = edgeEffect; int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); int maxScroll = childWidth - mHorizontalScrollView.getWidth(); - mHorizontalScrollView.scrollTo(maxScroll - 100, 0); - mHorizontalScrollView.fling(10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); } + + static class WatchedEdgeEffect extends EdgeEffect { + public CountDownLatch onAbsorbLatch = new CountDownLatch(1); + + WatchedEdgeEffect(Context context) { + super(context); + } + + @Override + public void onAbsorb(int velocity) { + super.onAbsorb(velocity); + onAbsorbLatch.countDown(); + } + } } diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 263e563bc224..6229530dc33f 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -154,7 +155,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).applyAsyncUnsafe(); } @@ -171,7 +173,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).applyAsyncUnsafe(); } @@ -241,7 +244,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -261,7 +265,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -281,7 +286,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -299,7 +305,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -310,7 +317,7 @@ public class SystemPerformanceHinterTests { mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON); // Verify we never call SF and perf manager since session1 is already running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); - verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -318,7 +325,7 @@ public class SystemPerformanceHinterTests { session2.close(); // Verify we have not cleaned up because session1 is still running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); - verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -330,7 +337,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -348,7 +356,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -363,7 +372,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_CATEGORY_HIGH)); + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction, never()).setEarlyWakeupStart(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -378,13 +388,15 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction, never()).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), anyInt()); verify(mTransaction, never()).setFrameRateCategory( eq(mSecondaryDisplayRoot), - anyInt()); + anyInt(), + eq(false)); verify(mTransaction, never()).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); @@ -401,7 +413,8 @@ public class SystemPerformanceHinterTests { eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_CATEGORY_DEFAULT)); + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 2cbeaa17332c..f846ac50d5fb 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -34,6 +34,7 @@ import org.junit.runners.Suite; KernelSingleUidTimeReaderTest.class, LongArrayMultiStateCounterTest.class, LongMultiStateCounterTest.class, + MonotonicClockTest.class, PowerProfileTest.class, PowerStatsTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java new file mode 100644 index 000000000000..7951270461d7 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -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.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.Xml; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MonotonicClockTest { + private final MockClock mClock = new MockClock(); + + @Test + public void persistence() throws IOException { + MonotonicClock monotonicClock = new MonotonicClock(1000, mClock); + mClock.realtime = 234; + + assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + monotonicClock.writeXml(out, Xml.newFastSerializer()); + String xml = out.toString(); + assertThat(xml).contains("timeshift=\"1234\""); + + mClock.realtime = 42; + MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock); + newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()), + Xml.newFastPullParser()); + + mClock.realtime = 2000; + assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000); + } +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9c6528785584..b71aaf3fc2e6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1801,6 +1801,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-504637678": { + "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d", + "level": "VERBOSE", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "-503656156": { "message": "Update process config of %s to new config %s", "level": "VERBOSE", @@ -3739,6 +3745,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityClientController.java" }, + "1309365288": { + "message": "Removing dim surface %s on transaction %s", + "level": "DEBUG", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "1316533291": { "message": "State movement: %s from:%s to:%s reason:%s", "level": "VERBOSE", @@ -4003,6 +4015,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1620751818": { + "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d", + "level": "DEBUG", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "1621562070": { "message": " startWCT=%s", "level": "VERBOSE", @@ -4560,6 +4578,9 @@ "WM_DEBUG_CONTENT_RECORDING": { "tag": "WindowManager" }, + "WM_DEBUG_DIMMER": { + "tag": "WindowManager" + }, "WM_DEBUG_DRAW": { "tag": "WindowManager" }, diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index 63d1f6d6f2d6..db37a38756d2 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -7,6 +7,12 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +aconfig_declarations { + name: "framework_graphics_flags", + package: "com.android.graphics.flags", + srcs: ["android/framework_graphics.aconfig"], +} + filegroup { name: "framework-graphics-nonupdatable-sources", srcs: [ diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig new file mode 100644 index 000000000000..e030dad6bf14 --- /dev/null +++ b/graphics/java/android/framework_graphics.aconfig @@ -0,0 +1,8 @@ +package: "com.android.graphics.flags" + +flag { + name: "exact_compute_bounds" + namespace: "framework_graphics" + description: "Add a function without unused exact param for computeBounds." + bug: "304478551" +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index b5fb13db4ae4..0a6fb8424094 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.graphics.hwui.flags.Flags; + import libcore.util.NativeAllocationRegistry; /** @@ -125,6 +128,7 @@ public final class Gainmap implements Parcelable { * Creates a new gainmap using the provided gainmap as the metadata source and the provided * bitmap as the replacement for the gainmapContents */ + @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA) public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) { this(gainmapContents, nCreateCopy(gainmap.mNativePtr)); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index db1cc446b2d6..9fde0fd6e6ab 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1609,7 +1609,7 @@ public class Paint { /** * Returns the color of the shadow layer. * - * @return the shadow layer's color encoded as a {@link ColorLong}. + * @return the shadow layer's color encoded as a {@code ColorLong}. * @see #setShadowLayer(float,float,float,int) * @see #setShadowLayer(float,float,float,long) * @see Color diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index c9c1b23d874c..deb89faf3419 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -16,11 +16,14 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import com.android.graphics.flags.Flags; + import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -309,6 +312,7 @@ public class Path { * * @param bounds Returns the computed bounds of the path's control points. */ + @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS) public void computeBounds(@NonNull RectF bounds) { nComputeBounds(mNativePath, bounds); } diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 4461f39fd006..c2a7a84ad809 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -109,9 +109,8 @@ final class RippleShader extends RuntimeShader { + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + " vec2 uv = p * in_resolutionScale;\n" + " vec2 densityUv = uv - mod(uv, in_noiseScale);\n" - + " float turbulence = turbulence(uv, in_turbulencePhase);\n" - + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha " - + "* turbulence;\n" + + " float turb = turbulence(uv, in_turbulencePhase);\n" + + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n" + " float fade = min(fadeIn, 1. - fadeOutRipple);\n" + " float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade " + "* in_color.a;\n" diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index f5e5803d4796..dc1773bd7290 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -39,12 +39,14 @@ public final class LineBreakConfig { /** * No hyphenation preference is specified. * + * <p> * This is a special value of hyphenation preference indicating no hyphenation preference is * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of * overridden config will be kept if the hyphenation preference of overriding config is * {@link #HYPHENATION_UNSPECIFIED}. * + * <p> * <pre> * val override = LineBreakConfig.Builder() * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) @@ -57,6 +59,7 @@ public final class LineBreakConfig { * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. * </pre> * + * <p> * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text * layout/rendering. */ @@ -89,6 +92,7 @@ public final class LineBreakConfig { /** * No line break style is specified. * + * <p> * This is a special value of line break style indicating no style value is specified. * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config @@ -107,6 +111,7 @@ public final class LineBreakConfig { * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. * </pre> * + * <p> * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text * layout/rendering. */ @@ -270,6 +275,8 @@ public final class LineBreakConfig { /** * Resets this builder to the given config state. * + * @param config a config value used for resetting. {@code null} is allowed. If {@code null} + * is passed, all configs are reset to unspecified. * @return This {@code Builder}. * @hide */ diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index dc2e794a1df4..0e3fb163ef75 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -249,7 +249,7 @@ public class LineBreaker { * @param useBoundsForWidth True for using bounding box, false for advances. * @return this builder instance * @see Layout#getUseBoundsForWidth() - * @see StaticLayout.Builder#setUseBoundsForWidth(boolean) + * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean) */ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) { diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 0e198d5c56ec..e6de5978ceb0 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -322,7 +322,7 @@ public final class PixelCopy { } /** - * Returns the {@link CopyResultStatus} of the copy request. + * Returns the status of the copy request. */ public @CopyResultStatus int getStatus() { return mStatus; diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 31c2eb2efaed..b7ea04fdfe07 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -128,25 +128,6 @@ public class AndroidKeyStoreMaintenance { } /** - * Queries user state from Keystore 2.0. - * - * @param userId - Android user id of the user. - * @return UserState enum variant as integer if successful or an error - */ - public static int getState(int userId) { - StrictMode.noteDiskRead(); - try { - return getService().getState(userId); - } catch (ServiceSpecificException e) { - Log.e(TAG, "getState failed", e); - return e.errorCode; - } catch (Exception e) { - Log.e(TAG, "Can not connect to keystore", e); - return SYSTEM_ERROR; - } - } - - /** * Informs Keystore 2.0 that an off body event was detected. */ public static void onDeviceOffBody() { diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 8045f55f6b4c..11b827117aa3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -19,8 +19,6 @@ package android.security; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.StrictMode; -import android.os.UserHandle; -import android.security.maintenance.UserState; /** * @hide This should not be made public in its present form because it @@ -37,15 +35,6 @@ public class KeyStore { // Used for UID field to indicate the calling UID. public static final int UID_SELF = -1; - // States - public enum State { - @UnsupportedAppUsage - UNLOCKED, - @UnsupportedAppUsage - LOCKED, - UNINITIALIZED - }; - private static final KeyStore KEY_STORE = new KeyStore(); @UnsupportedAppUsage @@ -55,28 +44,6 @@ public class KeyStore { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public State state(int userId) { - int userState = AndroidKeyStoreMaintenance.getState(userId); - switch (userState) { - case UserState.UNINITIALIZED: - return KeyStore.State.UNINITIALIZED; - case UserState.LSKF_UNLOCKED: - return KeyStore.State.UNLOCKED; - case UserState.LSKF_LOCKED: - return KeyStore.State.LOCKED; - default: - throw new AssertionError(userState); - } - } - - /** @hide */ - @UnsupportedAppUsage - public State state() { - return state(UserHandle.myUserId()); - } - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public byte[] get(String key) { return null; } diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java index 253d70465720..5825facee021 100644 --- a/keystore/java/android/security/KeyStoreException.java +++ b/keystore/java/android/security/KeyStoreException.java @@ -319,7 +319,7 @@ public class KeyStoreException extends Exception { /** * Returns one of the error codes exported by the class. * - * @return a public error code, one of the values in {@link PublicErrorCode}. + * @return a public error code */ @PublicErrorCode public int getNumericErrorCode() { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 96c257b304a0..1ba41b106f56 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -75,16 +75,18 @@ import javax.security.auth.x500.X500Principal; * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey} * interfaces. * - * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in - * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not - * support storing key pairs without a certificate. The subject, serial number, and validity dates - * of the certificate can be customized in this spec. The self-signed certificate may be replaced at - * a later time by a certificate signed by a Certificate Authority (CA). + * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android + * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing + * key pairs without a certificate. The subject, serial number, and validity dates of the + * certificate can be customized in this spec. The certificate may be replaced at a later time by a + * certificate signed by a Certificate Authority (CA). * - * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the - * certificate will be created with an invalid signature which will not verify. Such a certificate - * is still useful because it provides access to the public key. To generate a valid signature for - * the certificate the key needs to be authorized for all of the following: + * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])}, + * generated certificate may be self-signed. If a private key is not authorized to sign the + * certificate, then the certificate will be created with an invalid signature which will not + * verify. Such a certificate is still useful because it provides access to the public key. To + * generate a valid signature for the certificate the key needs to be authorized for all of the + * following: * <ul> * <li>{@link KeyProperties#PURPOSE_SIGN},</li> * <li>operation without requiring the user to be authenticated (see @@ -989,12 +991,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be * used. Attempts to use the key for any other purpose will be rejected. * - * <p>If the set of purposes for which the key can be used does not contain - * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by - * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an - * invalid signature. This is OK if the certificate is only used for obtaining the - * public key from Android KeyStore. - * * <p>See {@link KeyProperties}.{@code PURPOSE} flags. */ public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) { @@ -1140,7 +1136,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the subject used for the self-signed certificate of the generated key pair. + * Sets the subject used for the certificate of the generated key pair. * * <p>By default, the subject is {@code CN=fake}. */ @@ -1154,7 +1150,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the serial number used for the self-signed certificate of the generated key pair. + * Sets the serial number used for the certificate of the generated key pair. * * <p>By default, the serial number is {@code 1}. */ @@ -1168,8 +1164,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the start of the validity period for the self-signed certificate of the generated - * key pair. + * Sets the start of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 1970}. */ @@ -1183,8 +1178,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the end of the validity period for the self-signed certificate of the generated key - * pair. + * Sets the end of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 2048}. */ diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt index d3254b7914e2..e4bf4c26dc7d 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/ktfmt_includes.txt @@ -1,3 +1,4 @@ ++services/permission +packages/SystemUI -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml index da8abde8407f..8d2e28b9492f 100644 --- a/libs/WindowManager/Shell/res/values-television/config.xml +++ b/libs/WindowManager/Shell/res/values-television/config.xml @@ -45,13 +45,13 @@ <integer name="config_pipForceCloseDelay">5000</integer> <!-- Animation duration when exit starting window: fade out icon --> - <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer> + <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer> <!-- Animation delay when exit starting window: reveal app --> - <integer name="starting_window_app_reveal_anim_delay">0</integer> + <integer name="starting_window_app_reveal_anim_delay">200</integer> <!-- Animation duration when exit starting window: reveal app --> - <integer name="starting_window_app_reveal_anim_duration">500</integer> + <integer name="starting_window_app_reveal_anim_duration">300</integer> <!-- Default animation type when hiding the starting window. The possible values are: - 0 for radial vanish + slide up diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 06ce37148eaf..8cf869b175ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mTransitions.addHandler(this); } - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - boolean containsEmbeddingSplit = false; - boolean containsNonEmbeddedChange = false; - final List<TransitionInfo.Change> changes = info.getChanges(); - for (int i = changes.size() - 1; i >= 0; i--) { - final TransitionInfo.Change change = changes.get(i); - if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { - containsNonEmbeddedChange = true; - } else if (!change.hasFlags(FLAG_FILLS_TASK)) { + /** Whether ActivityEmbeddingController should animate this transition. */ + public boolean shouldAnimate(@NonNull TransitionInfo info) { + boolean containsEmbeddingChange = false; + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags( + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { // Whether the Task contains any ActivityEmbedding split before or after the // transition. - containsEmbeddingSplit = true; + containsEmbeddingChange = true; } } - if (!containsEmbeddingSplit) { + if (!containsEmbeddingChange) { // Let the system to play the default animation if there is no ActivityEmbedding split // window. This allows to play the app customized animation when there is no embedding, // such as the device is in a folded state. return false; } - if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + + if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) { return false; } + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); if (options != null // Scene-transition will be handled by app side. @@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return false; } + return true; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (!shouldAnimate(info)) return false; + // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); @@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mAnimationRunner.cancelAnimationFromMerge(); } + /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */ + private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) { + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + return true; + } + } + return false; + } + private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) { final Rect nonClosingEmbeddedArea = new Rect(); for (int i = changes.size() - 1; i >= 0; i--) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2241c343a208..ac5ba51ec139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1784,13 +1784,14 @@ public class BubbleStackView extends FrameLayout mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition); mStackAnimationController.setStackPosition(startPosition); mExpandedAnimationController.setCollapsePoint(startPosition); - // Set the translation x so that this bubble will animate in from the same side they - // expand / collapse on. - bubble.getIconView().setTranslationX(startPosition.x); } else if (firstBubble) { mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); } + // Set the view translation x so that this bubble will animate in from the same side they + // expand / collapse on. + bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x); + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index c51af46accdb..ea7b2e92fefb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -605,6 +605,7 @@ public abstract class WMShellBaseModule { @Provides static Transitions provideTransitions(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer organizer, TransactionPool pool, @@ -612,14 +613,13 @@ public abstract class WMShellBaseModule { @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellAnimationThread ShellExecutor animExecutor, - ShellCommandHandler shellCommandHandler, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) { // TODO(b/238217847): Force override shell init if registration is disabled shellInit = new ShellInit(mainExecutor); } - return new Transitions(context, shellInit, shellController, organizer, pool, - displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler, + return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer, + pool, displayController, mainExecutor, mainHandler, animExecutor, rootTaskDisplayAreaOrganizer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 5dfba5e7ff1d..a533ca5fa8fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -203,6 +204,7 @@ public abstract class WMShellModule { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -218,6 +220,7 @@ public abstract class WMShellModule { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, @@ -364,11 +367,12 @@ public abstract class WMShellModule { KeyguardTransitionHandler keyguardTransitionHandler, Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, pipTransitionController, recentsTransitionHandler, keyguardTransitionHandler, desktopTasksController, - unfoldHandler); + unfoldHandler, activityEmbeddingController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS new file mode 100644 index 000000000000..74a29ddad073 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS @@ -0,0 +1 @@ +hwwang@google.com 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 8a6403705c1c..1898ea737729 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,19 +17,28 @@ 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.common.pip.PipUtils; 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; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be * the successor of its sibling {@link Pip1Module}. @@ -42,8 +51,26 @@ public abstract class Pip2Module { @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm) { + PipBoundsAlgorithm pipBoundsAlgorithm, + Optional<PipController> pipController) { return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm); } + + @WMSingleton + @Provides + static Optional<PipController> providePipController(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + if (!PipUtils.isPip2ExperimentEnabled()) { + return Optional.empty(); + } else { + return Optional.ofNullable(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 000000000000..186cb615f4ec --- /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/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index d31476c63890..d277eef761e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -925,19 +925,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (toHome) wct.reorder(mRecentsTask, true /* toTop */); else wct.restoreTransientOrder(mRecentsTask); } - if (!toHome - // If a recents gesture starts on the 3p launcher, then the 3p launcher is the - // live tile (pausing app). If the gesture is "cancelled" we need to return to - // 3p launcher instead of "task-switching" away from it. - && (!mWillFinishToHome || mPausingSeparateHome) - && mPausingTasks != null && mState == STATE_NORMAL) { - if (mPausingSeparateHome) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to 3p home"); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to app"); - } + if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). @@ -969,6 +958,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { wct.restoreTransientOrder(mRecentsTask); } } else { + if (mPausingSeparateHome) { + if (mOpeningTasks.isEmpty()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " recents occluded 3p home"); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " switch task by recents on 3p home"); + } + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); // The general case: committing to recents, going home, or switching tasks. for (int i = 0; i < mOpeningTasks.size(); ++i) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 68ca2313f709..7a4834cb5adb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -23,6 +23,7 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -395,6 +396,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** @return whether this transition-request has the launch-adjacent flag. */ + public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + return triggerTask != null && triggerTask.baseIntent != null + && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; + } + /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -2459,10 +2467,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } - // When split in the background, it should be only opening/dismissing transition and - // would keep out not empty. Prevent intercepting all transitions for split screen when - // it is in the background and not identify to handle it. - return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; + if (!out.isEmpty()) { + // One of the cases above handled it + return out; + } else if (isSplitScreenVisible()) { + // If split is visible, only defer handling this transition if it's launching + // adjacent while there is already a split pair -- this may trigger PIP and + // that should be handled by the mixed handler. + final boolean deferTransition = requestHasLaunchAdjacentFlag(request) + && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + return !deferTransition ? out : null; + } + // Don't intercept the transition if we are not handling it as a part of one of the + // cases above and it is not already visible + return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. 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 226fe08a2f19..918a5a4bd53e 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 @@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; @@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private final KeyguardTransitionHandler mKeyguardHandler; private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; + private ActivityEmbeddingController mActivityEmbeddingController; private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; @@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Recents Transition while in desktop mode. */ static final int TYPE_RECENTS_DURING_DESKTOP = 6; - /** Fuld/Unfold transition. */ + /** Fold/Unfold transition. */ static final int TYPE_UNFOLD = 7; + /** Enter pip from one of the Activity Embedding windows. */ + static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopTasksController> desktopTasksControllerOptional, - Optional<UnfoldTransitionHandler> unfoldHandler) { + Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController) { mPlayer = player; mKeyguardHandler = keyguardHandler; if (Transitions.ENABLE_SHELL_TRANSITIONS @@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); + mActivityEmbeddingController = activityEmbeddingController.orElse(null); }, this); } } @@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPipHandler.augmentRequest(transition, request, out); mSplitHandler.addEnterOrExitIfNeeded(request, out); return out; + } else if (request.getType() == TRANSIT_PIP + && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && ( + mActivityEmbeddingController != null)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " Got a PiP-enter request from an Activity Embedding split"); + mActiveTransitions.add(new MixedTransition( + MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); + // Postpone transition splitting to later. + WindowContainerTransaction out = new WindowContainerTransaction(); + return out; } else if (request.getRemoteTransition() != null && TransitionUtil.isOpeningType(request.getType()) && (request.getTriggerTask() == null @@ -345,6 +364,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); } } @@ -353,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction, + finishTransaction, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { return false; } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { @@ -398,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP from an Activity Embedding window"); + // Split into two transitions (wct) + TransitionInfo.Change pipChange = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } + } + + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct); + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + finishCallback.onTransitionFinished(mixed.mFinishWCT); + }; + + if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { + // Fallback to dispatching to other handlers. + return false; + } + + // PIP window should always be on the highest Z order. + if (pipChange != null) { + mixed.mInFlightSubAnimations = 2; + mPipHandler.startEnterAnimation( + pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, + finishCB); + } else { + mixed.mInFlightSubAnimations = 1; + } + + mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse, + startTransaction, finishTransaction, finishCB); + return true; + } + private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -809,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.end(); + mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { mPipHandler.end(); if (mixed.mLeftoversHandler != null) { @@ -849,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 8b050e524038..b1fc16ddf19b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -63,7 +63,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { @NonNull Transitions.TransitionFinishCallback finishCallback) { if (mTransition != transition) return false; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" - + " transition %s for #%d.", mRemote, info.getDebugId()); + + " transition %s for (#%d).", mRemote, info.getDebugId()); final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index 592b22a47bc4..ca2c3b4fbff1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -126,7 +126,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s", + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s", info.getDebugId(), pendingRemote); if (pendingRemote == null) return false; @@ -241,7 +241,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { if (remote == null) return null; mRequestedRemotes.put(transition, remote); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" - + " for %s: %s", transition, remote); + + " for (#%d) %s: %s", request.getDebugId(), transition, remote); return new WindowContainerTransaction(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index 87c438a5b37d..ba0ef20c412e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -19,13 +19,12 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import java.util.ArrayList; - /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList; * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { - final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - mSleepTransitions.remove(transition); - startTransaction.apply(); - finishCallback.onTransitionFinished(null); - return true; + if (info.hasChangesOrSideEffects()) { + Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); + return false; + } else { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - mSleepTransitions.add(transition); return new WindowContainerTransaction(); } - - @Override - public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishTransaction) { - mSleepTransitions.remove(transition); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 0d9a9e9f07ff..576bba96044c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -179,6 +179,7 @@ public class Transitions implements RemoteCallable<Transitions>, private final DefaultTransitionHandler mDefaultTransitionHandler; private final RemoteTransitionHandler mRemoteTransitionHandler; private final DisplayController mDisplayController; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final SleepHandler mSleepHandler = new SleepHandler(); @@ -188,9 +189,6 @@ public class Transitions implements RemoteCallable<Transitions>, /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); - @Nullable - private final ShellCommandHandler mShellCommandHandler; - private final ArrayList<TransitionObserver> mObservers = new ArrayList<>(); /** List of {@link Runnable} instances to run when the last active transition has finished. */ @@ -237,7 +235,7 @@ public class Transitions implements RemoteCallable<Transitions>, @Override public String toString() { if (mInfo != null && mInfo.getDebugId() >= 0) { - return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack(); + return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack(); } return mToken.toString() + "@" + getTrack(); } @@ -275,13 +273,14 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor) { - this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, - mainHandler, animExecutor, null, + this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool, + displayController, mainExecutor, mainHandler, animExecutor, new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit)); } public Transitions(@NonNull Context context, @NonNull ShellInit shellInit, + @Nullable ShellCommandHandler shellCommandHandler, @NonNull ShellController shellController, @NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @@ -289,7 +288,6 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, - @Nullable ShellCommandHandler shellCommandHandler, @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) { mOrganizer = organizer; mContext = context; @@ -300,13 +298,13 @@ public class Transitions implements RemoteCallable<Transitions>, mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit, displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer); mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor); + mShellCommandHandler = shellCommandHandler; mShellController = shellController; // The very last handler (0 in the list) should be the default one. mHandlers.add(mDefaultTransitionHandler); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default"); // Next lowest priority is remote transitions. mHandlers.add(mRemoteTransitionHandler); - mShellCommandHandler = shellCommandHandler; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); shellInit.addInitCallback(this::onInit, this); } @@ -339,9 +337,8 @@ public class Transitions implements RemoteCallable<Transitions>, TransitionMetrics.getInstance(); } - if (mShellCommandHandler != null) { - mShellCommandHandler.addCommandCallback("transitions", this, this); - } + mShellCommandHandler.addCommandCallback("transitions", this, this); + mShellCommandHandler.addDumpCallback(this::dump, this); } public boolean isRegistered() { @@ -655,8 +652,8 @@ public class Transitions implements RemoteCallable<Transitions>, void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", - transitionToken, info); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", + info.getDebugId(), transitionToken, info); final int activeIdx = findByToken(mPendingTransitions, transitionToken); if (activeIdx < 0) { throw new IllegalStateException("Got transitionReady for non-pending transition " @@ -1073,8 +1070,8 @@ public class Transitions implements RemoteCallable<Transitions>, void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", - transitionToken, request); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", + request.getDebugId(), transitionToken, request); if (isTransitionKnown(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); } @@ -1475,4 +1472,68 @@ public class Transitions implements RemoteCallable<Transitions>, pw.println(prefix + "tracing"); mTracer.printShellCommandHelp(pw, prefix + " "); } + + private void dump(@NonNull PrintWriter pw, String prefix) { + pw.println(prefix + TAG); + + final String innerPrefix = prefix + " "; + pw.println(prefix + "Handlers:"); + for (TransitionHandler handler : mHandlers) { + pw.print(innerPrefix); + pw.print(handler.getClass().getSimpleName()); + pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); + } + + pw.println(prefix + "Observers:"); + for (TransitionObserver observer : mObservers) { + pw.print(innerPrefix); + pw.println(observer.getClass().getSimpleName()); + } + + pw.println(prefix + "Pending Transitions:"); + for (ActiveTransition transition : mPendingTransitions) { + pw.print(innerPrefix + "token="); + pw.println(transition.mToken); + pw.print(innerPrefix + "id="); + pw.println(transition.mInfo != null + ? transition.mInfo.getDebugId() + : -1); + pw.print(innerPrefix + "handler="); + pw.println(transition.mHandler != null + ? transition.mHandler.getClass().getSimpleName() + : null); + } + if (mPendingTransitions.isEmpty()) { + pw.println(innerPrefix + "none"); + } + + pw.println(prefix + "Ready-during-sync Transitions:"); + for (ActiveTransition transition : mReadyDuringSync) { + pw.print(innerPrefix + "token="); + pw.println(transition.mToken); + pw.print(innerPrefix + "id="); + pw.println(transition.mInfo != null + ? transition.mInfo.getDebugId() + : -1); + pw.print(innerPrefix + "handler="); + pw.println(transition.mHandler != null + ? transition.mHandler.getClass().getSimpleName() + : null); + } + if (mReadyDuringSync.isEmpty()) { + pw.println(innerPrefix + "none"); + } + + pw.println(prefix + "Tracks:"); + for (int i = 0; i < mTracks.size(); i++) { + final ActiveTransition transition = mTracks.get(i).mActiveTransition; + pw.println(innerPrefix + "Track #" + i); + pw.print(innerPrefix + "active="); + pw.println(transition); + if (transition != null) { + pw.print(innerPrefix + "hander="); + pw.println(transition.mHandler); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 82fc0f49c143..aff35a347183 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -231,4 +231,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL int getCaptionHeightId(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } + + @Override + int getCaptionViewId() { + return R.id.caption; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index bf99ab35cdd7..ca91d580b4ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -50,6 +51,8 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -67,6 +70,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; 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.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -131,6 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener = new DesktopModeKeyguardChangeListener(); private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final DisplayInsetsController mDisplayInsetsController; + private boolean mInImmersiveMode; public DesktopModeWindowDecorViewModel( Context context, @@ -141,6 +147,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -156,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, @@ -176,6 +184,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -191,6 +200,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskOrganizer = taskOrganizer; mShellController = shellController; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mSyncQueue = syncQueue; mTransitions = transitions; mDesktopTasksController = desktopTasksController; @@ -213,6 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }); mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), + new DesktopModeOnInsetsChangedListener()); } @Override @@ -655,9 +667,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); if (DesktopModeStatus.isEnabled()) { - if (relevantDecor == null + if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM - || mTransitionDragActive) { + || mTransitionDragActive)) { handleCaptionThroughStatusBar(ev, relevantDecor); } } @@ -1051,6 +1063,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return mIsKeyguardVisible && mIsKeyguardOccluded; } } + + @VisibleForTesting + class DesktopModeOnInsetsChangedListener implements + DisplayInsetsController.OnInsetsChangedListener { + @Override + public void insetsChanged(InsetsState insetsState) { + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + final DesktopModeWindowDecoration decor = getFocusedDecor(); + if (decor == null) { + return; + } + // If status bar inset is visible, top task is not in immersive mode + final boolean inImmersiveMode = !source.isVisible(); + // Calls WindowDecoration#relayout if decoration visibility needs to be updated + if (inImmersiveMode != mInImmersiveMode) { + decor.relayout(decor.mTaskInfo); + mInImmersiveMode = inImmersiveMode; + } + + return; + } + } + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 380b59e84485..248e83747c48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -638,6 +638,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode)); } + @Override + int getCaptionViewId() { + return R.id.desktop_mode_caption; + } + /** * Add transition to mTransitionsPausingRelayout */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 335a5886ba28..d0e647b30a96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.statusBars; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; @@ -30,6 +31,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.view.Display; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; @@ -119,6 +122,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; + private boolean mIsCaptionVisible; private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); @@ -225,6 +229,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .inflate(params.mLayoutResId, null); } + updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId); + final Resources resources = mDecorWindowContext.getResources(); final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); @@ -272,18 +278,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); - mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), - mCaptionInsetsRect); + if (mIsCaptionVisible) { + mCaptionInsetsRect.bottom = + mCaptionInsetsRect.top + captionHeight + params.mCaptionY; + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); + } else { + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.mandatorySystemGestures()); + } } else { startT.hide(mCaptionContainerSurface); } // Task surface itself - float shadowRadius = loadDimension(resources, params.mShadowRadiusId); + float shadowRadius; final Point taskPosition = mTaskInfo.positionInParent; if (isFullscreen) { // Setting the task crop to the width/height stops input events from being sent to @@ -294,9 +308,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // drag-resized by the window decoration. startT.setWindowCrop(mTaskSurface, null); finishT.setWindowCrop(mTaskSurface, null); + // Shadow is not needed for fullscreen tasks + shadowRadius = 0; } else { startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + shadowRadius = loadDimension(resources, params.mShadowRadiusId); } startT.setShadowRadius(mTaskSurface, shadowRadius) .show(mTaskSurface); @@ -348,10 +365,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + /** + * Checks if task has entered/exited immersive mode and requires a change in caption visibility. + */ + private void updateCaptionVisibility(View rootView, int displayId) { + final InsetsState insetsState = mDisplayController.getInsetsState(displayId); + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + mIsCaptionVisible = source.isVisible(); + setCaptionVisibility(rootView, mIsCaptionVisible); + + return; + } + } + + private void setCaptionVisibility(View rootView, boolean visible) { + if (rootView == null) { + return; + } + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = rootView.findViewById(getCaptionViewId()); + captionView.setVisibility(v); + } + int getCaptionHeightId(@WindowingMode int windowingMode) { return Resources.ID_NULL; } + int getCaptionViewId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -466,7 +514,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ public void addCaptionInset(WindowContainerTransaction wct) { final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode()); - if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL + || !mIsCaptionVisible) { return; } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index b13e9a248575..1df11369a049 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 b9c9049e08c4..da83d4c0a122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1169,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1188,6 +1188,65 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testSleepTransition_withKeyguardGoingAway_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If keyguard-going-away flag set, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + @Test + public void testSleepTransition_withChanges_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a transition with some changes + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) + .addChange(TRANSIT_OPEN).build(); + info.setTrack(0); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is an actual change, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + + @Test + public void testSleepTransition_empty_SyncBySleepHandler() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, 0x0, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is nothing to actually play, it should not be offered to handlers. + assertEquals(0, mDefaultHandler.activeCount()); + } + + @Test public void testMultipleTracks() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8eaf5a004c0a..57aa47e85556 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -33,8 +33,12 @@ import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.InputChannel import android.view.InputMonitor +import android.view.InsetsSource +import android.view.InsetsState import android.view.SurfaceControl import android.view.SurfaceView +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars import androidx.core.content.getSystemService import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -42,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder 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.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -53,10 +58,12 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -68,6 +75,7 @@ import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier + /** Tests of [DesktopModeWindowDecorViewModel] */ @SmallTest @RunWith(AndroidTestingRunner::class) @@ -80,6 +88,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var mockInputMonitor: InputMonitor @@ -97,6 +106,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } private lateinit var shellInit: ShellInit + private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel @Before @@ -111,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTaskOrganizer, mockDisplayController, mockShellController, + displayInsetsController, mockSyncQueue, mockTransitions, Optional.of(mockDesktopTasksController), @@ -131,6 +142,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1]) shellInit.init() + + val listenerCaptor = + argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>() + verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture()) + desktopModeOnInsetsChangedListener = listenerCaptor.firstValue } @Test @@ -274,6 +290,67 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration).addTransitionPausingRelayout(transition) } + @Test + fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout occurs when status bar inset visibility changes + verify(decoration, times(1)).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add navigation bar insets source + val insetsState = InsetsState() + val navigationBarInsetsSourceId = 1 + val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars()) + navigationBarInsetsSource.isVisible = false + insetsState.addSource(navigationBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout does not occur when non-status bar inset changes visibility + verify(decoration, never()).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout runs only once when status bar inset visibility changes. + verify(decoration, times(1)).relayout(task) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, @@ -313,6 +390,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDesktopModeWindowDecorFactory.create( any(), any(), any(), eq(task), any(), any(), any(), any(), any()) ).thenReturn(decoration) + decoration.mTaskInfo = task + whenever(decoration.isFocused).thenReturn(task.isFocused) return decoration } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index fcb7863429d6..8061aa3f844a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -18,6 +18,9 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.captionBar; +import static android.view.WindowInsets.Type.mandatorySystemGestures; +import static android.view.WindowInsets.Type.statusBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder; @@ -25,6 +28,8 @@ import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceCon import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertTrue; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; @@ -51,6 +56,7 @@ import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; import android.view.Display; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; @@ -94,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); private static final int CORNER_RADIUS = 20; + private static final int STATUS_BAR_INSET_SOURCE_ID = 0; private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -118,6 +125,7 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); + private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; @@ -141,6 +149,11 @@ public class WindowDecorationTests extends ShellTestCase { .create(any(), any(), any()); when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); + when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + + // Add status bar inset so that WindowDecoration does not think task is in immersive mode + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); + doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); } @Test @@ -537,12 +550,41 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f}); + verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f}); mockitoSession.finishMocking(); } @Test + public void testInsetsAddedWhenCaptionIsVisible() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()) + .isVisible()); + assertTrue(mInsetsState.sourceSize() == 1); + assertTrue(mInsetsState.sourceAt(0).getType() == statusBars()); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar()), any()); + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures()), any()); + } + + @Test public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( DesktopModeStatus.class).strictness(LENIENT).startMocking(); @@ -581,6 +623,33 @@ public class WindowDecorationTests extends ShellTestCase { mockitoSession.finishMocking(); } + + @Test + public void testInsetsRemovedWhenCaptionIsHidden() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures())); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d0d3c5e7ece1..e672b983d509 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -20,3 +20,10 @@ flag { description: "APIs to help enable animations involving gainmaps" bug: "296482289" } + +flag { + name: "gainmap_constructor_with_metadata" + namespace: "core_graphics" + description: "APIs to create a new gainmap with a bitmap for metadata." + bug: "304478551" +} diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt index 794082124344..c396a2032eed 100644 --- a/libs/hwui/api/current.txt +++ b/libs/hwui/api/current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.graphics { public class ColorMatrix { diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/module-lib-current.txt +++ b/libs/hwui/api/module-lib-current.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/module-lib-removed.txt +++ b/libs/hwui/api/module-lib-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/removed.txt +++ b/libs/hwui/api/removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/system-current.txt +++ b/libs/hwui/api/system-current.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/system-removed.txt +++ b/libs/hwui/api/system-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 90850d36b612..22c586248705 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -26,6 +26,7 @@ #include <gui/TraceUtils.h> #include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> +#include <include/gpu/ganesh/vk/GrVkDirectContext.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> @@ -435,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, options.fContextDeleteContext = this; options.fContextDeleteProc = onGrContextReleased; - return GrDirectContext::MakeVulkan(backendContext, options); + return GrDirectContexts::MakeVulkan(backendContext, options); } VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java index 16c3f2ed27d8..2dd67f0c12aa 100644 --- a/location/java/android/location/GnssSignalType.java +++ b/location/java/android/location/GnssSignalType.java @@ -34,8 +34,7 @@ public final class GnssSignalType implements Parcelable { /** * Creates a {@link GnssSignalType} with a full list of parameters. * - * @param constellationType the constellation type as defined in - * {@link GnssStatus.ConstellationType} + * @param constellationType the constellation type * @param carrierFrequencyHz the carrier frequency in Hz * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()} */ @@ -66,7 +65,7 @@ public final class GnssSignalType implements Parcelable { this.mCodeType = codeType; } - /** Returns the {@link GnssStatus.ConstellationType}. */ + /** Returns the constellation type. */ @GnssStatus.ConstellationType public int getConstellationType() { return mConstellationType; diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index ceb3858eb0b3..b002bbf20c08 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1282,10 +1282,8 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_OUT_BACK_CENTER}, * {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT}, * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}. - * <p> For a valid {@link AudioTrack} channel position mask, - * the following conditions apply: - * <br> (1) at most eight channel positions may be used; - * <br> (2) right/left pairs should be matched. + * <p> For output or {@link AudioTrack}, channel position masks which do not contain + * matched left/right pairs are invalid. * <p> For input or {@link AudioRecord}, the mask should be * {@link AudioFormat#CHANNEL_IN_MONO} or * {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d9ed6a8fa158..5b880797b7fd 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/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 7eb0c76fbf4e..4a5b4f2de20f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -74,8 +74,7 @@ interface IMediaRouterService { // Methods for MediaRouter2Manager List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager); - RoutingSessionInfo getSystemSessionInfoForPackage( - IMediaRouter2Manager manager, String packageName); + RoutingSessionInfo getSystemSessionInfoForPackage(String packageName); void registerManager(IMediaRouter2Manager manager, String packageName); void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8c635807022b..159427bc2796 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) @@ -2059,9 +2059,7 @@ public final class MediaRouter2 { public RoutingSessionInfo getSystemSessionInfo() { RoutingSessionInfo result; try { - result = - mMediaRouterService.getSystemSessionInfoForPackage( - mClient, mClientPackageName); + result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 3abfc6297b0b..830708cb38b2 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -377,7 +377,7 @@ public final class MediaRouter2Manager { @Nullable public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) { try { - return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName); + return mMediaRouterService.getSystemSessionInfoForPackage(packageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index e9a6ed4dcf08..9ced2a45a6f7 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -16,6 +16,7 @@ package android.media.audiopolicy; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +49,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.media.audio.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -417,6 +419,7 @@ public class AudioPolicy { * @return {@link AudioManager#SUCCESS} if the update was successful, * {@link AudioManager#ERROR} otherwise. */ + @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules( @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) { diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index a354f9180e09..c9cfa670cf02 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 6e2aaabf4b04..c54bfce840aa 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -16,6 +16,9 @@ package android.media.midi; +import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; @@ -38,7 +41,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 @@ -54,9 +57,11 @@ import java.util.List; * android:resource="@xml/device_info" /> * </service></pre> */ +@FlaggedApi(FLAG_VIRTUAL_UMP) public abstract class MidiUmpDeviceService extends Service { private static final String TAG = "MidiUmpDeviceService"; + @FlaggedApi(FLAG_VIRTUAL_UMP) public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; private IMidiManager mMidiManager; @@ -75,6 +80,7 @@ public abstract class MidiUmpDeviceService extends Service { } }; + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public void onCreate() { mMidiManager = IMidiManager.Stub.asInterface( @@ -112,6 +118,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers(); /** @@ -120,6 +127,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return the list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @NonNull List<MidiReceiver> getOutputPortReceivers() { if (mServer == null) { return new ArrayList<MidiReceiver>(); @@ -132,6 +140,7 @@ public abstract class MidiUmpDeviceService extends Service { * Returns the {@link MidiDeviceInfo} instance for this service * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @Nullable MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; } @@ -140,6 +149,7 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the {@link MidiDeviceStatus} has changed * @param status the current status of the MIDI device */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) { } @@ -147,9 +157,11 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the virtual MIDI device running in this service has been closed by * all its clients */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onClose() { } + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public @Nullable IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d294601b44cc..31e65eb13926 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)") + oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS index cc9be9c5126a..880ec8fdef88 100644 --- a/media/java/android/media/projection/OWNERS +++ b/media/java/android/media/projection/OWNERS @@ -4,3 +4,4 @@ michaelwr@google.com santoscordon@google.com chaviw@google.com nmusgrave@google.com +dakinola@google.com diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 13f7743f8d26..2db4be86bf91 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/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index e88655894156..7d79a6c266e4 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml index 132028ce98dc..91c92cc13d1b 100644 --- a/media/tests/MediaFrameworkTest/AndroidTest.xml +++ b/media/tests/MediaFrameworkTest/AndroidTest.xml @@ -23,5 +23,6 @@ <option name="package" value="com.android.mediaframeworktest" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> + <option name="isolated-storage" value="false"/> </test> </configuration> diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java index 9be7004c5701..30edfa40802b 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java @@ -44,7 +44,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { @Override public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); - addMediaMetadataRetrieverStateUnitTests(suite); addMediaRecorderStateUnitTests(suite); addMediaPlayerStateUnitTests(suite); addMediaScannerUnitTests(suite); @@ -70,11 +69,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { } // Running all unit tests checking the state machine may be time-consuming. - private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { - suite.addTestSuite(MediaMetadataRetrieverTest.class); - } - - // Running all unit tests checking the state machine may be time-consuming. private void addMediaRecorderStateUnitTests(TestSuite suite) { suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class); suite.addTestSuite(MediaRecorderResetStateUnitTest.class); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java index bdca4744ff74..f70d2d1f8ae7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java @@ -16,26 +16,34 @@ package com.android.mediaframeworktest.unit; +import static org.junit.Assert.assertTrue; + import android.graphics.Bitmap; import android.media.MediaMetadataRetriever; -import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; +import androidx.test.runner.AndroidJUnit4; + import com.android.mediaframeworktest.MediaNames; import com.android.mediaframeworktest.MediaProfileReader; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.FileOutputStream; import java.io.IOException; -public class MediaMetadataRetrieverTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class MediaMetadataRetrieverTest { private static final String TAG = "MediaMetadataRetrieverTest"; // Test album art extraction. @MediumTest - public static void testGetEmbeddedPicture() throws Exception { + @Test + public void testGetEmbeddedPicture() throws Exception { Log.v(TAG, "testGetEmbeddedPicture starts."); MediaMetadataRetriever retriever = new MediaMetadataRetriever(); boolean supportWMA = MediaProfileReader.getWMAEnable(); @@ -78,7 +86,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { // Test frame capture @LargeTest - public static void testThumbnailCapture() throws Exception { + @Test + public void testThumbnailCapture() throws Exception { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); boolean supportWMA = MediaProfileReader.getWMAEnable(); boolean supportWMV = MediaProfileReader.getWMVEnable(); @@ -134,7 +143,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { } @LargeTest - public static void testMetadataRetrieval() throws Exception { + @Test + public void testMetadataRetrieval() throws Exception { boolean supportWMA = MediaProfileReader.getWMAEnable(); boolean supportWMV = MediaProfileReader.getWMVEnable(); boolean hasFailed = false; @@ -169,7 +179,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { // If the specified call order and valid media file is used, no exception // should be thrown. @MediumTest - public static void testBasicNormalMethodCallSequence() throws Exception { + @Test + public void testBasicNormalMethodCallSequence() throws Exception { boolean hasFailed = false; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { @@ -197,7 +208,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { // If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must // return null. @MediumTest - public static void testBasicAbnormalMethodCallSequence() { + @Test + public void testBasicAbnormalMethodCallSequence() { boolean hasFailed = false; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) { @@ -213,7 +225,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { // Test setDataSource() @MediumTest - public static void testSetDataSource() throws IOException { + @Test + public void testSetDataSource() throws IOException { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); boolean hasFailed = false; diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 58bcd1d49c69..e71597a27a39 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -24,6 +24,11 @@ aidl_interface { backend: { java: { sdk_version: "module_current", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + ], + }, rust: { enabled: true, diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 222877bbe9e9..88f1204641ff 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -102,6 +102,8 @@ <item name="android:layout_height">36dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> + <item name="android:paddingLeft">6dp</item> + <item name="android:paddingRight">6dp</item> <item name="android:background">@drawable/btn_negative_multiple_devices</item> <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e..3409c29d3c2c 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 18a8edc5e447..0144b6ea9040 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 8de12d03a92a..976a3ad69901 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/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 1cc2867ecf0e..0b7a568dd2f2 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -26,7 +26,7 @@ plugins { } allprojects { - extra["jetpackComposeVersion"] = "1.6.0-alpha02" + extra["jetpackComposeVersion"] = "1.6.0-alpha07" } subprojects { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 01596d2bc004..d62b4907e96c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -40,6 +40,7 @@ import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider +import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.preference.PreferencePageProvider @@ -74,6 +75,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { PreferencePageProvider, SwitchPreferencePageProvider, MainSwitchPreferencePageProvider, + ListPreferencePageProvider, TwoTargetSwitchPreferencePageProvider, ArgumentPageProvider, SliderPageProvider, diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt index df1d7d18555a..b001caddd000 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt @@ -18,8 +18,8 @@ package com.android.settingslib.spa.gallery.button import android.os.Bundle import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Launch import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview @@ -47,7 +47,7 @@ object ActionButtonPageProvider : SettingsPageProvider { override fun Page(arguments: Bundle?) { RegularScaffold(title = TITLE) { val actionButtons = listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {}, ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {}, ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {}, ) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt new file mode 100644 index 000000000000..43b6d0b05696 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt @@ -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.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.ListPreference +import com.android.settingslib.spa.widget.preference.ListPreferenceModel +import com.android.settingslib.spa.widget.preference.ListPreferenceOption +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow + +private const val TITLE = "Sample ListPreference" + +object ListPreferencePageProvider : SettingsPageProvider { + override val name = "ListPreference" + private val owner = createSettingsPage() + + override fun buildEntry(arguments: Bundle?) = listOf( + SettingsEntryBuilder.create("ListPreference", owner) + .setUiLayoutFn { + SampleListPreference() + }.build(), + SettingsEntryBuilder.create("ListPreference not changeable", owner) + .setUiLayoutFn { + SampleNotChangeableListPreference() + }.build(), + ) + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + override fun getTitle(arguments: Bundle?) = TITLE +} + +@Composable +private fun SampleListPreference() { + val selectedId = rememberSaveable { mutableIntStateOf(1) } + ListPreference(remember { + object : ListPreferenceModel { + override val title = "Preferred network type" + override val options = listOf( + ListPreferenceOption(id = 1, text = "5G (recommended)"), + ListPreferenceOption(id = 2, text = "LTE"), + ListPreferenceOption(id = 3, text = "3G"), + ) + override val selectedId = selectedId + override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it } + } + }) +} + +@Composable +private fun SampleNotChangeableListPreference() { + val selectedId = rememberSaveable { mutableIntStateOf(1) } + val enableFlow = flow { + var enabled = true + while (true) { + delay(3.seconds) + enabled = !enabled + emit(enabled) + } + } + val enabled = enableFlow.collectAsStateWithLifecycle(initialValue = true) + ListPreference(remember { + object : ListPreferenceModel { + override val title = "Preferred network type" + override val enabled = enabled + override val options = listOf( + ListPreferenceOption(id = 1, text = "5G (recommended)"), + ListPreferenceOption(id = 2, text = "LTE"), + ListPreferenceOption(id = 3, text = "3G"), + ) + override val selectedId = selectedId + override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it } + } + }) +} + +@Preview +@Composable +private fun ListPreferencePagePreview() { + SettingsTheme { + ListPreferencePageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt index eddede752d06..ce9678bab684 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt @@ -36,6 +36,7 @@ object PreferenceMainPageProvider : SettingsPageProvider { PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), TwoTargetSwitchPreferencePageProvider.buildInjectEntry() .setLink(fromPage = owner).build(), ) diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 8b563362a77b..aafae5f2129b 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,11 +15,11 @@ # [versions] -agp = "8.1.1" +agp = "8.1.2" compose-compiler = "1.5.1" dexmaker-mockito = "2.28.3" kotlin = "1.9.0" -truth = "1.1" +truth = "1.1.5" [libraries] dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" } diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index da04f426c68f..ce89de6ffd65 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,7 +16,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index b8105113af7b..b73bbd8b80e1 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,13 +57,13 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-alpha04") + api("androidx.compose.material3:material3:1.2.0-alpha09") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.7.1") + api("androidx.navigation:navigation-compose:2.7.4") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.7.0-alpha03") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 7962e601999a..4088ffd43986 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -40,8 +40,18 @@ object SettingsDimension { /** The size when app icon is displayed in App info page. */ val appIconInfoSize = 48.dp + /** The vertical padding for buttons. */ + val buttonPaddingVertical = 12.dp + /** The [PaddingValues] for buttons. */ - val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp) + val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = buttonPaddingVertical) + + /** The horizontal padding for dialog items. */ + val dialogItemPaddingHorizontal = itemPaddingStart + + /** The [PaddingValues] for dialog items. */ + val dialogItemPadding = + PaddingValues(horizontal = dialogItemPaddingHorizontal, vertical = buttonPaddingVertical) /** The sizes info of illustration widget. */ val illustrationMaxWidth = 412.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt index c8faef6d6703..a9cd0e9cd2e9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt @@ -16,10 +16,15 @@ package com.android.settingslib.spa.framework.theme +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha + object SettingsOpacity { const val Full = 1f const val Disabled = 0.38f const val Divider = 0.2f const val SurfaceTone = 0.14f const val Hint = 0.9f + + fun Modifier.alphaForEnabled(enabled: Boolean) = alpha(if (enabled) Full else Disabled) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt index c66e20a335ef..8c862d401c6b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt @@ -22,5 +22,5 @@ import androidx.compose.ui.unit.dp object SettingsShape { val CornerMedium = RoundedCornerShape(12.dp) - val CornerLarge = RoundedCornerShape(24.dp) + val CornerExtraLarge = RoundedCornerShape(28.dp) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 1ad075c11985..979cf3bddae6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -65,7 +65,7 @@ fun ActionButtons(actionButtons: List<ActionButton>) { Row( Modifier .padding(SettingsDimension.buttonPadding) - .clip(SettingsShape.CornerLarge) + .clip(SettingsShape.CornerExtraLarge) .height(IntrinsicSize.Min) ) { for ((index, actionButton) in actionButtons.withIndex()) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt new file mode 100644 index 000000000000..8b172da08dd2 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.dialog + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.Dialog +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.widget.ui.SettingsTitle + +@Composable +fun SettingsDialog( + title: String, + onDismissRequest: () -> Unit, + content: @Composable () -> Unit, +) { + Dialog(onDismissRequest = onDismissRequest) { + Card(shape = SettingsShape.CornerExtraLarge) { + Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) { + Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) { + SettingsTitle(title = title, useMediumWeight = true) + } + content() + } + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt index 6330ddf5bea4..4d42fbae24fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt @@ -29,13 +29,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsDimension -import com.android.settingslib.spa.framework.theme.SettingsOpacity +import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.ui.SettingsTitle @@ -57,8 +56,7 @@ internal fun BaseLayout( .padding(end = paddingEnd), verticalAlignment = Alignment.CenterVertically, ) { - val alphaModifier = - Modifier.alpha(if (enabled.value) SettingsOpacity.Full else SettingsOpacity.Disabled) + val alphaModifier = Modifier.alphaForEnabled(enabled.value) BaseIcon(icon, alphaModifier, paddingStart) Titles( title = title, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt new file mode 100644 index 000000000000..19779f67ca48 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.preference + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.material3.RadioButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.IntState +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.dialog.SettingsDialog +import com.android.settingslib.spa.widget.ui.SettingsDialogItem + +data class ListPreferenceOption( + val id: Int, + val text: String, +) + +/** + * The widget model for [ListPreference] widget. + */ +interface ListPreferenceModel { + /** + * The title of this [ListPreference]. + */ + val title: String + + /** + * The icon of this [ListPreference]. + * + * Default is `null` which means no icon. + */ + val icon: (@Composable () -> Unit)? + get() = null + + /** + * Indicates whether this [ListPreference] is enabled. + * + * Disabled [ListPreference] will be displayed in disabled style. + */ + val enabled: State<Boolean> + get() = stateOf(true) + + val options: List<ListPreferenceOption> + + val selectedId: IntState + + val onIdSelected: (id: Int) -> Unit +} + +@Composable +fun ListPreference(model: ListPreferenceModel) { + var dialogOpened by rememberSaveable { mutableStateOf(false) } + if (dialogOpened) { + SettingsDialog( + title = model.title, + onDismissRequest = { dialogOpened = false }, + ) { + Column(modifier = Modifier.selectableGroup()) { + for (option in model.options) { + Radio(option, model.selectedId, model.enabled) { + dialogOpened = false + model.onIdSelected(it) + } + } + } + } + } + Preference(model = remember(model) { + object : PreferenceModel { + override val title = model.title + override val summary = derivedStateOf { + model.options.find { it.id == model.selectedId.intValue }?.text ?: "" + } + override val icon = model.icon + override val enabled = model.enabled + override val onClick = { dialogOpened = true }.takeIf { model.options.isNotEmpty() } + } + }) +} + +@Composable +private fun Radio( + option: ListPreferenceOption, + selectedId: IntState, + enabledState: State<Boolean>, + onIdSelected: (id: Int) -> Unit, +) { + val selected = option.id == selectedId.intValue + val enabled = enabledState.value + Row( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = selected, + enabled = enabled, + onClick = { onIdSelected(option.id) }, + role = Role.RadioButton, + ) + .padding(SettingsDimension.dialogItemPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton(selected = selected, onClick = null, enabled = enabled) + Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd)) + SettingsDialogItem(text = option.text, enabled = enabled) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index 3e04b16f08cf..0c16c8bc7229 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -39,7 +39,7 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) { true -> MaterialTheme.colorScheme.primaryContainer else -> MaterialTheme.colorScheme.secondaryContainer }, - shape = SettingsShape.CornerLarge, + shape = SettingsShape.CornerExtraLarge, ) { InternalSwitchPreference( title = model.title, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 6ef45900a103..5f320f7ade3f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -18,19 +18,14 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.scale -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.LayoutDirection import com.android.settingslib.spa.framework.compose.LocalNavController -import androidx.compose.material.icons.automirrored.outlined.ArrowBack /** Action that navigates back to last page. */ @Composable @@ -55,7 +50,6 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { Icon( imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, - modifier = Modifier.autoMirrored(), ) } } @@ -81,10 +75,3 @@ internal fun ClearAction(onClick: () -> Unit) { ) } } - -private fun Modifier.autoMirrored() = composed { - when (LocalLayoutDirection.current) { - LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f) - else -> this - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index 57319e760c69..7f1acffe7a8a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.toMediumWeight @@ -48,6 +49,17 @@ fun SettingsTitle(title: String, useMediumWeight: Boolean = false) { } @Composable +fun SettingsDialogItem(text: String, enabled: Boolean = true) { + Text( + text = text, + modifier = Modifier.alphaForEnabled(enabled), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + overflow = TextOverflow.Ellipsis, + ) +} + +@Composable fun SettingsBody( body: String, maxLines: Int = Int.MAX_VALUE, @@ -82,6 +94,9 @@ fun PlaceholderTitle(title: String) { private fun BasePreferencePreview() { SettingsTheme { Column(Modifier.width(100.dp)) { + SettingsTitle( + title = "Title", + ) SettingsBody( body = "Long long long long long long text", ) diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt new file mode 100644 index 000000000000..c7582b2601af --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.dialog + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.onDialogText +import com.android.settingslib.spa.widget.ui.SettingsDialogItem +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsDialogTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + SettingsDialog(title = TITLE, onDismissRequest = {}) {} + } + + composeTestRule.onDialogText(TITLE).assertIsDisplayed() + } + + @Test + fun text_displayed() { + composeTestRule.setContent { + SettingsDialog(title = "", onDismissRequest = {}) { + SettingsDialogItem(text = TEXT) + } + } + + composeTestRule.onDialogText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt new file mode 100644 index 000000000000..997a02369d9f --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.preference + +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.testutils.onDialogText +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ListPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = emptyList<ListPreferenceOption>() + override val selectedId = mutableIntStateOf(0) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun summary_showSelectedText() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText("A").assertIsDisplayed() + } + + @Test + fun click_optionsIsEmpty_notShowDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = emptyList<ListPreferenceOption>() + override val selectedId = mutableIntStateOf(0) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertDoesNotExist() + } + + @Test + fun click_notEnabled_notShowDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val enabled = stateOf(false) + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertDoesNotExist() + } + + @Test + fun click_optionsNotEmpty_showDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertIsDisplayed() + } + + @Test + fun select() { + val selectedId = mutableIntStateOf(1) + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf( + ListPreferenceOption(id = 1, text = "A"), + ListPreferenceOption(id = 2, text = "B"), + ) + override val selectedId = selectedId + override val onIdSelected = { id: Int -> selectedId.intValue = id } + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + composeTestRule.onDialogText("B").performClick() + + composeTestRule.onNodeWithText("B").assertIsDisplayed() + } + + @Test + fun select_dialogOpenThenDisable_itemAlsoDisabled() { + val selectedId = mutableIntStateOf(1) + val enabledState = mutableStateOf(true) + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val enabled = enabledState + override val options = listOf( + ListPreferenceOption(id = 1, text = "A"), + ListPreferenceOption(id = 2, text = "B"), + ) + override val selectedId = selectedId + override val onIdSelected = { id: Int -> selectedId.intValue = id } + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + enabledState.value = false + + composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled() + } + + private companion object { + const val TITLE = "Title" + } +} diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts index 50243dcd8c9b..cce82354a51f 100644 --- a/packages/SettingsLib/Spa/testutils/build.gradle.kts +++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { api("androidx.arch.core:core-testing:2.2.0-alpha01") api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-runtime-testing") - api("org.mockito.kotlin:mockito-kotlin:5.1.0") + api("org.mockito.kotlin:mockito-kotlin:2.2.11") api("org.mockito:mockito-core") { version { strictly("2.28.2") diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index f54de1514fcf..09cb98e22af3 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -22,6 +22,8 @@ import android.os.UserHandle import android.os.UserManager import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin @@ -32,15 +34,15 @@ import kotlinx.coroutines.flow.flowOn import com.android.settingslib.widget.restricted.R data class Restrictions( - val userId: Int, + val userId: Int = UserHandle.myUserId(), val keys: List<String>, ) sealed interface RestrictedMode -object NoRestricted : RestrictedMode +data object NoRestricted : RestrictedMode -object BaseUserRestricted : RestrictedMode +data object BaseUserRestricted : RestrictedMode interface BlockedByAdmin : RestrictedMode { fun getSummary(checked: Boolean?): String @@ -79,6 +81,17 @@ interface RestrictionsProvider { typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider +@Composable +internal fun RestrictionsProviderFactory.rememberRestrictedMode( + restrictions: Restrictions, +): State<RestrictedMode?> { + val context = LocalContext.current + val restrictionsProvider = remember(restrictions) { + this(context, restrictions) + } + return restrictionsProvider.restrictedModeState() +} + internal class RestrictionsProviderImpl( private val context: Context, private val restrictions: Restrictions, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 1fa854a4c09e..17e970845f58 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -45,6 +45,7 @@ import com.android.settingslib.spaprivileged.model.app.userId import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference import kotlinx.coroutines.flow.Flow @@ -149,14 +150,13 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable fun getSummary(record: T): State<String> { - val restrictionsProvider = remember(record.app.userId) { - val restrictions = Restrictions( + val restrictions = remember(record.app.userId) { + Restrictions( userId = record.app.userId, keys = listModel.switchRestrictionKeys, ) - restrictionsProviderFactory(context, restrictions) } - val restrictedMode = restrictionsProvider.restrictedModeState() + val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions) val allowed = listModel.isAllowed(record) return remember { derivedStateOf { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt new file mode 100644 index 000000000000..50490c0b887d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt @@ -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.settingslib.spaprivileged.template.preference + +import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode + +@Composable +fun RestrictedPreference( + model: PreferenceModel, + restrictions: Restrictions, +) { + RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl) +} + +@VisibleForTesting +@Composable +internal fun RestrictedPreference( + model: PreferenceModel, + restrictions: Restrictions, + restrictionsProviderFactory: RestrictionsProviderFactory, +) { + if (restrictions.keys.isEmpty()) { + Preference(model) + return + } + val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value + val restrictedSwitchModel = remember(restrictedMode) { + RestrictedPreferenceModel(model, restrictedMode) + } + restrictedSwitchModel.RestrictionWrapper { + Preference(restrictedSwitchModel) + } +} + +private class RestrictedPreferenceModel( + model: PreferenceModel, + private val restrictedMode: RestrictedMode?, +) : PreferenceModel { + override val title = model.title + override val summary = model.summary + override val icon = model.icon + + override val enabled = when (restrictedMode) { + NoRestricted -> model.enabled + else -> stateOf(false) + } + + override val onClick = when (restrictedMode) { + NoRestricted -> model.onClick + // Need to passthrough onClick for clickable semantics, although since enabled is false so + // this will not be called. + BaseUserRestricted -> model.onClick + else -> null + } + + @Composable + fun RestrictionWrapper(content: @Composable () -> Unit) { + if (restrictedMode !is BlockedByAdmin) { + content() + return + } + Box( + Modifier + .clickable( + role = Role.Button, + onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, + ) + ) { content() } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index e77dcd4d9cc4..2129403c2d85 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spaprivileged.template.preference import android.content.Context +import androidx.annotation.VisibleForTesting import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable @@ -40,22 +41,29 @@ import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode @Composable fun RestrictedSwitchPreference( model: SwitchPreferenceModel, restrictions: Restrictions, - restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl, +) { + RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl) +} + +@VisibleForTesting +@Composable +internal fun RestrictedSwitchPreference( + model: SwitchPreferenceModel, + restrictions: Restrictions, + restrictionsProviderFactory: RestrictionsProviderFactory, ) { if (restrictions.keys.isEmpty()) { SwitchPreference(model) return } val context = LocalContext.current - val restrictionsProvider = remember(restrictions) { - restrictionsProviderFactory(context, restrictions) - } - val restrictedMode = restrictionsProvider.restrictedModeState().value + val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value val restrictedSwitchModel = remember(restrictedMode) { RestrictedSwitchPreferenceModel(context, model, restrictedMode) } @@ -112,8 +120,8 @@ private class RestrictedSwitchPreferenceModel( override val onCheckedChange = when (restrictedMode) { null -> null is NoRestricted -> model.onCheckedChange - // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although - // since changeable is false this will not be called. + // Need to passthrough onCheckedChange for toggleable semantics, although since changeable + // is false so this will not be called. is BaseUserRestricted -> model.onCheckedChange // Pass null since semantics ToggleableState is provided in RestrictionWrapper. is BlockedByAdmin -> null diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt index 86b6f027997d..f9abefc11e24 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt @@ -16,15 +16,15 @@ package com.android.settingslib.spaprivileged.template.scaffold +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode @Composable fun MoreOptionsScope.RestrictedMenuItem( @@ -35,6 +35,7 @@ fun MoreOptionsScope.RestrictedMenuItem( RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl) } +@VisibleForTesting @Composable internal fun MoreOptionsScope.RestrictedMenuItemImpl( text: String, @@ -42,12 +43,8 @@ internal fun MoreOptionsScope.RestrictedMenuItemImpl( onClick: () -> Unit, restrictionsProviderFactory: RestrictionsProviderFactory, ) { - val context = LocalContext.current - val restrictionsProvider = remember(restrictions) { - restrictionsProviderFactory(context, restrictions) - } - val restrictedMode = restrictionsProvider.restrictedModeState().value - MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) { + val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value + MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) { when (restrictedMode) { is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent() else -> onClick() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt new file mode 100644 index 000000000000..eadf0ca0686d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.preference + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin +import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RestrictedPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val fakeBlockedByAdmin = FakeBlockedByAdmin() + + private val fakeRestrictionsProvider = FakeRestrictionsProvider() + + private var clicked = false + + private val preferenceModel = object : PreferenceModel { + override val title = TITLE + override val onClick = { clicked = true } + } + + @Test + fun whenRestrictionsKeysIsEmpty_enabled() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun whenRestrictionsKeysIsEmpty_clickable() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + assertThat(clicked).isTrue() + } + + @Test + fun whenNoRestricted_enabled() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = NoRestricted + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun whenNoRestricted_clickable() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = NoRestricted + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + assertThat(clicked).isTrue() + } + + @Test + fun whenBaseUserRestricted_disabled() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled() + } + + @Test + fun whenBaseUserRestricted_notClickable() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + assertThat(clicked).isFalse() + } + + @Test + fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun whenBlockedByAdmin_click() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue() + } + + private fun setContent(restrictions: Restrictions) { + composeTestRule.setContent { + RestrictedPreference(preferenceModel, restrictions) { _, _ -> + fakeRestrictionsProvider + } + } + } + + private companion object { + const val TITLE = "Title" + const val USER_ID = 0 + const val RESTRICTION_KEY = "restriction_key" + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 412a3424bbd1..ce0772ff84a1 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; @@ -67,8 +68,7 @@ public class Utils { static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled"; - @VisibleForTesting - static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = + public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = "incompatible_charger_warning_disabled"; private static Signature[] sSystemSignature; @@ -598,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/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 7f1f3f613bce..2032328cbc04 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -110,7 +110,8 @@ public class BatteryStatus { } /** - * Determine whether the device is plugged in wireless. */ + * Determine whether the device is plugged in wireless. + */ public boolean isPluggedInWireless() { return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } @@ -185,6 +186,22 @@ public class BatteryStatus { return status == BATTERY_STATUS_FULL || level >= 100; } + /** + * Whether or not the device is charged. Note that some devices never return 100% for battery + * level, so this allows either battery level or status to determine if the battery is charged. + * + * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + */ + public static boolean isCharged(int status, int level, int scale) { + var batteryLevel = getBatteryLevel(level, scale); + return isCharged(status, batteryLevel); + } + /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { if (batteryChangedIntent == null) { @@ -193,6 +210,14 @@ public class BatteryStatus { final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + return getBatteryLevel(level, scale); + } + + /** + * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL + * and EXTRA_SCALE. + */ + public static int getBatteryLevel(int level, int scale) { return scale == 0 ? BATTERY_LEVEL_UNKNOWN : Math.round((level / (float) scale) * 100f); @@ -253,11 +278,22 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * SEVERE_LOW_BATTERY_THRESHOLD} + * SEVERE_LOW_BATTERY_THRESHOLD} */ public static boolean isSevereLowBattery(Intent batteryChangedIntent) { - int level = getBatteryLevel(batteryChangedIntent); - return level <= SEVERE_LOW_BATTERY_THRESHOLD; + int batteryLevel = getBatteryLevel(batteryChangedIntent); + return isSevereLowBattery(batteryLevel); + } + + /** + * Whether the battery is severe low or not. + * + * @param batteryLevel the value of battery level + * @return {@code true} if the battery level is less or equal to {@link + * SEVERE_LOW_BATTERY_THRESHOLD} + */ + public static boolean isSevereLowBattery(int batteryLevel) { + return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD; } /** @@ -265,11 +301,21 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * EXTREME_LOW_BATTERY_THRESHOLD} + * EXTREME_LOW_BATTERY_THRESHOLD} */ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) { int level = getBatteryLevel(batteryChangedIntent); - return level <= EXTREME_LOW_BATTERY_THRESHOLD; + return isExtremeLowBattery(level); + } + + /** + * Whether the battery is extreme low or not. + * + * @return {@code true} if the {@code batteryLevel} is less or equal to + * {@link EXTREME_LOW_BATTERY_THRESHOLD} + */ + public static boolean isExtremeLowBattery(int batteryLevel) { + return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD; } /** @@ -277,7 +323,7 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell - * defend, or temp defend + * defend, or temp defend */ public static boolean isBatteryDefender(Intent batteryChangedIntent) { int chargingStatus = @@ -298,9 +344,8 @@ public class BatteryStatus { } /** - * Gets the max charging current and max charging voltage form {@link - * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link - * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}. + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. * * @param context the application context * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED} @@ -308,7 +353,29 @@ public class BatteryStatus { * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} */ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) { - final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent); + final int maxChargingMicroCurrent = + batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt); + } + + /** + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. + * + * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the + * extra of {@link Intent.Action_BATTERY_CHANGED} + * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra + * of {@link Intent.Action_BATTERY_CHANGED} + * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link + * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} + */ + public static int calculateChargingSpeed( + Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) { + final int maxChargingMicroWatt = + calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt); + if (maxChargingMicroWatt <= 0) { return CHARGING_UNKNOWN; } else if (maxChargingMicroWatt @@ -326,6 +393,12 @@ public class BatteryStatus { final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt); + } + + private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp, + int maxChargingMicroVolt) { if (maxChargingMicroVolt <= 0) { maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 41afc7b8b194..a63bbdf36fa8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; + /** Returns the device name for the given {@code routeInfo}. */ + public static String getSystemRouteNameFromType( + @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { + CharSequence name; + switch (routeInfo.getType()) { + case TYPE_WIRED_HEADSET: + case TYPE_WIRED_HEADPHONES: + case TYPE_USB_DEVICE: + case TYPE_USB_HEADSET: + case TYPE_USB_ACCESSORY: + name = context.getString(R.string.media_transfer_wired_usb_device_name); + break; + case TYPE_DOCK: + name = context.getString(R.string.media_transfer_dock_speaker_device_name); + break; + case TYPE_BUILTIN_SPEAKER: + name = context.getString(R.string.media_transfer_this_device_name); + break; + case TYPE_HDMI: + name = context.getString(R.string.media_transfer_external_device_name); + break; + default: + name = context.getString(R.string.media_transfer_default_device_name); + break; + } + return name.toString(); + } + PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { - CharSequence name; - switch (mRouteInfo.getType()) { - case TYPE_WIRED_HEADSET: - case TYPE_WIRED_HEADPHONES: - case TYPE_USB_DEVICE: - case TYPE_USB_HEADSET: - case TYPE_USB_ACCESSORY: - name = mContext.getString(R.string.media_transfer_wired_usb_device_name); - break; - case TYPE_DOCK: - name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); - break; - case TYPE_BUILTIN_SPEAKER: - name = mContext.getString(R.string.media_transfer_this_device_name); - break; - case TYPE_HDMI: - name = mContext.getString(R.string.media_transfer_external_device_name); - break; - default: - name = mContext.getString(R.string.media_transfer_default_device_name); - break; - } - return name.toString(); + return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java new file mode 100644 index 000000000000..a9fd380c2733 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils.shadow; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.hardware.display.ColorDisplayManager; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ColorDisplayManager.class) +public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager { + + private boolean mIsReduceBrightColorsActivated; + + @Implementation + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setReduceBrightColorsActivated(boolean activated) { + mIsReduceBrightColorsActivated = activated; + return true; + } + + @Implementation + @SystemApi + public boolean isReduceBrightColorsActivated() { + return mIsReduceBrightColorsActivated; + } + +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index fe39c4febc80..59c3cd38a97d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -84,6 +84,7 @@ public class SystemSettings { Settings.System.RING_VIBRATION_INTENSITY, Settings.System.HAPTIC_FEEDBACK_INTENSITY, Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, + Settings.System.KEYBOARD_VIBRATION_ENABLED, Settings.System.HAPTIC_FEEDBACK_ENABLED, Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE Settings.System.DISPLAY_COLOR_MODE, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index eba74ab14f3d..572303a813bf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -138,6 +138,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(System.KEYBOARD_VIBRATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.RINGTONE, URI_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a9673cc2cf05..9d3200dc340d 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -60,6 +60,7 @@ systemui_compose_java_defaults { // except for SystemUI-core. // Copied from compose/features/Android.bp. static_libs: [ + "CommunalLayoutLib", "PlatformComposeCore", "PlatformComposeSceneTransitionLayout", @@ -242,9 +243,6 @@ filegroup { "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt", "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt", - /* Log fakes */ - "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt", - /* QS fakes */ "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt", ], @@ -436,6 +434,7 @@ android_library { "SystemUI-statsd", "SettingsLib", "com_android_systemui_flags_lib", + "flag-junit-base", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9bfc4be0f30d..58816310d495 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/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0623d4a4b7fb..03f7c9968a1d 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -144,9 +144,32 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" }, { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { "exclude-annotation": "android.platform.test.annotations.Postsubmit" } ] } + ], + // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged + "sysui-screenshot-test-staged": [ + { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "include-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + } ] } diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index c1390b252418..dc4208e87207 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -2,8 +2,7 @@ aconfig_declarations { name: "com_android_systemui_flags", package: "com.android.systemui", srcs: [ - "systemui.aconfig", - "accessibility.aconfig", + "*.aconfig", ], } diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig new file mode 100644 index 000000000000..2c6ff979cc7f --- /dev/null +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -0,0 +1,8 @@ +package: "com.android.systemui" + +flag { + name: "communal_hub" + namespace: "communal" + description: "Enables the communal hub experience" + bug: "304584416" +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2509cfd4af40..211af908a877 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -29,3 +29,10 @@ flag { "Notification Manager Service" bug: "299448097" } + +flag { + name: "scene_container" + namespace: "systemui" + description: "Enables the scene container framework go/flexiglass." + bug: "283121968" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4aac27932924..4ea57a8cc007 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -893,7 +893,7 @@ class ActivityLaunchAnimator( return } - Log.i(TAG, "Remote animation timed out") + Log.wtf(TAG, "Remote animation timed out") timedOut = true if (DEBUG_LAUNCH_ANIMATION) { diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp new file mode 100644 index 000000000000..88dad6623d03 --- /dev/null +++ b/packages/SystemUI/communal/layout/Android.bp @@ -0,0 +1,35 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "CommunalLayoutLib", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.arch.core_core-runtime", + "androidx.compose.animation_animation-graphics", + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "jsr330", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", + ], + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml new file mode 100644 index 000000000000..141be0762ae9 --- /dev/null +++ b/packages/SystemUI/communal/layout/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?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 package="com.android.systemui.communal.layout" /> diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt new file mode 100644 index 000000000000..df87d19db5a6 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt @@ -0,0 +1,66 @@ +/* + * 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.communal.layout + +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard + +/** Computes the arrangement of cards. */ +class CommunalLayoutEngine { + companion object { + /** + * Determines the size that each card should be rendered in, and distributes the cards into + * columns. + * + * Returns a nested list where the outer list contains columns, and the inner list contains + * cards in each column. + * + * Currently treats the first supported size as the size to be rendered in, ignoring other + * supported sizes. + */ + fun distributeCardsIntoColumns( + cards: List<CommunalGridLayoutCard>, + ): List<List<CommunalGridLayoutCardInfo>> { + val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() + + var capacityOfLastColumn = 0 + for (card in cards) { + val cardSize = card.supportedSizes.first() + if (capacityOfLastColumn >= cardSize.value) { + // Card fits in last column + capacityOfLastColumn -= cardSize.value + } else { + // Create a new column + result.add(arrayListOf()) + capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value + } + + result.last().add(CommunalGridLayoutCardInfo(card, cardSize)) + } + + return result + } + } + + /** + * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the + * card should be rendered in. + */ + data class CommunalGridLayoutCardInfo( + val card: CommunalGridLayoutCard, + val size: CommunalGridLayoutCard.Size, + ) +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt new file mode 100644 index 000000000000..4ed78b3b95ed --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt @@ -0,0 +1,64 @@ +/* + * 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.communal.layout.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.communal.layout.CommunalLayoutEngine +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig + +/** + * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size + * and follows a specific order based on its priority, ensuring a seamless layout without any gaps. + */ +@Composable +fun CommunalGridLayout( + modifier: Modifier, + layoutConfig: CommunalGridLayoutConfig, + communalCards: List<CommunalGridLayoutCard>, +) { + val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards) + LazyRow( + modifier = modifier.height(layoutConfig.gridHeight), + horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (column in columns) { + item { + Column( + modifier = Modifier.width(layoutConfig.cardWidth), + verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (cardInfo in column) { + Row( + modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), + ) { + cardInfo.card.Content(Modifier.fillMaxSize()) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt new file mode 100644 index 000000000000..ac8aa67fa4bf --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt @@ -0,0 +1,64 @@ +/* + * 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.communal.layout.ui.compose.config + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** A card that hosts content to be rendered in the communal grid layout. */ +abstract class CommunalGridLayoutCard { + /** + * Content to be hosted by the card. + * + * To host non-Compose views, see + * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. + */ + @Composable abstract fun Content(modifier: Modifier) + + /** + * Sizes supported by the card. + * + * If multiple sizes are available, they should be ranked in order of preference, from most to + * least preferred. + */ + abstract val supportedSizes: List<Size> + + /** + * Priority of the content hosted by the card. + * + * The value of priority is relative to other cards. Cards with a higher priority are generally + * ordered first. + */ + open val priority: Int = 0 + + /** + * Size of the card. + * + * @param value A numeric value that represents the size. Must be less than or equal to + * [Size.FULL]. + */ + enum class Size(val value: Int) { + /** The card takes up full height of the grid layout. */ + FULL(value = 6), + + /** The card takes up half of the vertical space of the grid layout. */ + HALF(value = 3), + + /** The card takes up a third of the vertical space of the grid layout. */ + THIRD(value = 2), + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt new file mode 100644 index 000000000000..143df838169b --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt @@ -0,0 +1,82 @@ +/* + * 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.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.times + +/** + * Configurations of the communal grid layout. + * + * The communal grid layout follows Material Design's responsive layout grid (see + * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided + * up by columns and gutters, and each card occupies one or multiple columns. + */ +data class CommunalGridLayoutConfig( + /** + * Size in dp of each grid column. + * + * Every card occupies one or more grid columns, which means that the width of each card is + * influenced by the size of the grid columns. + */ + val gridColumnSize: Dp, + + /** + * Size in dp of each grid gutter. + * + * A gutter is the space between columns that helps separate content. This is, therefore, also + * the size of the gaps between cards, both horizontally and vertically. + */ + val gridGutter: Dp, + + /** + * Height in dp of the grid layout. + * + * Cards with a full size take up the entire height of the grid layout. + */ + val gridHeight: Dp, + + /** + * Number of grid columns that each card occupies. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val gridColumnsPerCard: Int, +) { + /** + * Width in dp of each card. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1) + + /** Returns the height of a card in dp, based on its size. */ + fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp { + return when (cardSize) { + CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1) + CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2) + CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3) + } + } + + /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */ + private fun cardHeightBy(denominator: Int): Dp { + return (gridHeight - (denominator - 1) * gridGutter) / denominator + } +} diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp new file mode 100644 index 000000000000..9a05504cad8b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/Android.bp @@ -0,0 +1,47 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "CommunalLayoutLibTests", + srcs: [ + "**/*.kt", + ], + static_libs: [ + "CommunalLayoutLib", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "junit", + "kotlinx_coroutines_test", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "testables", + "truth", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + manifest: "AndroidManifest.xml", +} diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml new file mode 100644 index 000000000000..b19007c1ff1b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.communal.layout.tests"> + + <application android:debuggable="true" android:largeHeap="true"> + <uses-library android:name="android.test.mock" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.testing.TestableInstrumentation" + android:targetPackage="com.android.systemui.communal.layout.tests" + android:label="Tests for CommunalLayoutLib"> + </instrumentation> + +</manifest> diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml new file mode 100644 index 000000000000..1352b238f6fe --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration description="Runs tests for CommunalLayoutLib"> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="CommunalLayoutLibTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="CommunalLayoutLibTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.systemui.communal.layout.tests" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> + +</configuration> diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt new file mode 100644 index 000000000000..fdf65f5d5cc7 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt @@ -0,0 +1,99 @@ +package com.android.systemui.communal.layout + +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalLayoutEngineTest { + @Test + fun distribution_fullLayout() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + @Test + fun distribution_layoutWithGaps() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.THIRD, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + private fun assertDistribution( + cards: List<CommunalGridLayoutCard>, + expected: List<List<CommunalGridLayoutCard.Size>>, + ) { + val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) + + for (c in expected.indices) { + for (r in expected[c].indices) { + assertThat(result[c][r].size).isEqualTo(expected[c][r]) + } + } + } + + private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(size) + + @Composable + override fun Content(modifier: Modifier) { + Card(modifier = modifier, content = {}) + } + } + } +} diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt new file mode 100644 index 000000000000..946eeecbec5d --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt @@ -0,0 +1,63 @@ +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalGridLayoutConfigTest { + @Test + fun cardWidth() { + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 1, + ) + .cardWidth + ) + .isEqualTo(5.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 2, + ) + .cardWidth + ) + .isEqualTo(13.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 3, + ) + .cardWidth + ) + .isEqualTo(21.dp) + } + + @Test + fun cardHeight() { + val config = + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 2.dp, + gridHeight = 10.dp, + gridColumnsPerCard = 3, + ) + + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp) + } +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 5b4a8fb6ab7a..c6e429a79e86 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.lifecycle.LifecycleOwner +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene @@ -64,6 +65,13 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createCommunalView( + context: Context, + viewModel: CommunalViewModel, + ): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index ac599897553a..1722685f4287 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -30,6 +30,8 @@ import com.android.compose.theme.PlatformTheme import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider +import com.android.systemui.communal.ui.compose.CommunalHub +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -93,6 +95,15 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createCommunalView( + context: Context, + viewModel: CommunalViewModel, + ): View { + return ComposeView(context).apply { + setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } } + } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 796abf4b52d6..16c24375d14f 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -31,6 +31,7 @@ android_library { ], static_libs: [ + "CommunalLayoutLib", "SystemUI-core", "PlatformComposeCore", "PlatformComposeSceneTransitionLayout", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt new file mode 100644 index 000000000000..3d827fb5c9a6 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -0,0 +1,65 @@ +package com.android.systemui.communal.ui.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.integerResource +import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.res.R + +@Composable +fun CommunalHub( + modifier: Modifier = Modifier, + viewModel: CommunalViewModel, +) { + val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false) + Box( + modifier = modifier.fillMaxSize().background(Color.White), + ) { + CommunalGridLayout( + modifier = Modifier.align(Alignment.CenterStart), + layoutConfig = + CommunalGridLayoutConfig( + gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size), + gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size), + gridHeight = dimensionResource(R.dimen.communal_grid_height), + gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card), + ), + communalCards = if (showTutorial) tutorialContent else emptyList(), + ) + } +} + +private val tutorialContent = + listOf( + tutorialCard(CommunalGridLayoutCard.Size.FULL), + tutorialCard(CommunalGridLayoutCard.Size.THIRD), + tutorialCard(CommunalGridLayoutCard.Size.THIRD), + tutorialCard(CommunalGridLayoutCard.Size.THIRD), + tutorialCard(CommunalGridLayoutCard.Size.HALF), + tutorialCard(CommunalGridLayoutCard.Size.HALF), + tutorialCard(CommunalGridLayoutCard.Size.HALF), + tutorialCard(CommunalGridLayoutCard.Size.HALF), + ) + +private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(size) + + @Composable + override fun Content(modifier: Modifier) { + Card(modifier = modifier, content = {}) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 0d2ba2824846..f3bef7bf47da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -16,15 +16,10 @@ package com.android.systemui.communal.ui.compose -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import com.android.compose.animation.scene.SceneScope +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -38,7 +33,11 @@ import kotlinx.coroutines.flow.asStateFlow /** The communal scene shows glanceable hub when the device is locked and docked. */ @SysUISingleton -class CommunalScene @Inject constructor() : ComposableScene { +class CommunalScene +@Inject +constructor( + private val viewModel: CommunalViewModel, +) : ComposableScene { override val key = SceneKey.Communal override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = @@ -51,13 +50,6 @@ class CommunalScene @Inject constructor() : ComposableScene { @Composable override fun SceneScope.Content(modifier: Modifier) { - Box( - modifier = modifier.fillMaxSize().background(Color.White), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "Hello Communal!", - ) - } + CommunalHub(modifier, viewModel) } } 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 591fa76f423f..4bbb78b69392 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/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index a5e5aaa499f1..a9d2ee3cdfe6 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]; @@ -88,6 +92,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/proguard_common.flags b/packages/SystemUI/proguard_common.flags index be1e6554baf1..445bdc2c1936 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -2,45 +2,17 @@ # Needed to ensure callback field references are kept in their respective # owning classes when the downstream callback registrars only store weak refs. -# TODO(b/264686688): Handle these cases with more targeted annotations. --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - private com.android.keyguard.KeyguardUpdateMonitorCallback *; - private com.android.systemui.privacy.PrivacyConfig$Callback *; - private com.android.systemui.privacy.PrivacyItemController$Callback *; - private com.android.systemui.settings.UserTracker$Callback *; - private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; - private com.android.systemui.util.service.Observer$Callback *; - private com.android.systemui.util.service.ObservableServiceConnection$Callback *; -} -# Note that these rules are temporary companions to the above rules, required -# for cases like Kotlin where fields with anonymous types use the anonymous type -# rather than the supertype. --if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyConfig$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyItemController$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.settings.UserTracker$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.util.service.Observer$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +# Note that we restrict this to SysUISingleton classes, as other registering +# classes should either *always* unregister or *never* register from their +# constructor. We also keep callback class names for easier debugging. +-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } --if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml new file mode 100644 index 000000000000..910a74ad5faf --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml @@ -0,0 +1,45 @@ +<?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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:paddingMode="stack"> + <item + android:id="@android:id/background" + android:gravity="center_vertical|fill_horizontal"> + <shape + android:layout_width="match_parent" + android:layout_height="match_parent" + android:shape="rectangle"> + <corners android:radius="30dp" /> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" /> + </shape> + </item> + <item + android:id="@android:id/progress" + android:gravity="center_vertical|fill_horizontal"> + <clip> + <shape + android:layout_width="match_parent" + android:layout_height="match_parent" + android:shape="rectangle"> + <corners android:radius="30dp" /> + <solid android:color="?androidprv:attr/textColorPrimary" /> + </shape> + </clip> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml new file mode 100644 index 000000000000..183f0e591c91 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml @@ -0,0 +1,32 @@ +<?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. + ~ + --> + +<LinearLayout android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + android:layoutDirection="ltr" + android:gravity="center" + xmlns:android="http://schemas.android.com/apk/res/android"> + <ProgressBar + android:id="@+id/side_fps_progress_bar" + android:layout_width="55dp" + android:layout_height="10dp" + android:indeterminateOnly="false" + android:min="0" + android:max="100" + android:progressDrawable="@drawable/progress_bar" /> +</LinearLayout> 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 a1e2dc36278b..a8017f65c810 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/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml new file mode 100644 index 000000000000..c1062548aa89 --- /dev/null +++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml @@ -0,0 +1,25 @@ +<?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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true" + android:state_pressed="true" /> + <!-- RippleDrawable has default way of handling hover state with highlighting but it's not + consistent with our approach so we make highlighting invisible and instead do custom handling + of hover state on a different level --> + <item android:color="@color/transparent" android:state_hovered="true" /> + <item android:color="?android:attr/colorControlHighlight" /> +</selector>
\ No newline at end of file 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 000000000000..db25c89d1fa5 --- /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/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml index 265f575fc99c..ef3c61bda15a 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background.xml @@ -15,9 +15,25 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight"> + android:color="@color/qs_tile_ripple_color"> <item android:id="@android:id/mask" - android:drawable="@drawable/qs_tile_background_shape" /> - <item android:id="@id/background" - android:drawable="@drawable/qs_tile_background_shape"/> + android:drawable="@drawable/qs_tile_background_shape" + /> + <item android:id="@id/background"> + <layer-list> + <item + android:id="@+id/qs_tile_background_base" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@+id/qs_tile_background_overlay"> + <selector> + <item + android:state_hovered="true" + android:drawable="@drawable/qs_tile_background_shape" /> + <item + android:state_focused="true" + android:drawable="@drawable/qs_tile_background_shape" /> + </selector> + </item> + </layer-list> + </item> </ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index a51c55ee965f..8cfcb689eced 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 ea3c012afc43..1f671ac4c875 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 1add90ff4083..9ac1e9f58dbc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -730,6 +730,9 @@ <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> + <!-- Component names of allowed communal widgets --> + <string-array name="config_communalWidgetAllowlist" translatable="false" /> + <!-- Component name of communal source service --> <string name="config_communalSourceComponent" translatable="false">@null</string> @@ -947,6 +950,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 +960,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 5a83c7d2dc2a..10fd8b579b16 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> @@ -760,6 +766,8 @@ <dimen name="keyguard_clock_switch_y_shift">14dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> + <!-- The amount to translate lockscreen elements on the GONE->AOD transition --> + <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen> <dimen name="notification_scrim_corner_radius">32dp</dimen> @@ -1355,6 +1363,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> @@ -1658,6 +1669,15 @@ <!-- Height percentage of the parent container occupied by the communal view --> <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> + <!-- Size of each communal grid column --> + <dimen name="communal_grid_column_size">64dp</dimen> + <!-- Size of each communal grid gutter between columns --> + <dimen name="communal_grid_gutter_size">16dp</dimen> + <!-- Height of the communal grid layout --> + <dimen name="communal_grid_height">630dp</dimen> + <!-- Number of columns for each communal card --> + <integer name="communal_grid_columns_per_card">6</integer> + <dimen name="drag_and_drop_icon_size">70dp</dimen> <dimen name="qs_tile_service_request_dialog_width">304dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 85b986486099..05f4334bbe89 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -229,6 +229,7 @@ <item type="id" name="privacy_dialog_manage_app_button" /> <!-- Communal mode --> + <item type="id" name="communal_hub" /> <item type="id" name="communal_widget_wrapper" /> <!-- Values assigned to the views in Biometrics Prompt --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 321594f41479..7a6d29ab3c1f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -209,6 +209,8 @@ <string name="screenshot_saved_title">Screenshot saved</string> <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] --> <string name="screenshot_failed_title">Couldn\'t save screenshot</string> + <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] --> + <string name="screenshot_failed_external_display_indication">External Display</string> <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] --> <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string> <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] --> @@ -711,8 +713,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 000000000000..4b430e146439 --- /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/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 80040a384b9d..631423e4b7fc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -155,6 +155,26 @@ public class ActivityManagerWrapper { } } + + /** + * Requests for a new snapshot to be taken for the given task, stores it in the cache, and + * returns a {@link ThumbnailData} with the result. + */ + @NonNull + public ThumbnailData takeTaskThumbnail(int taskId) { + TaskSnapshot snapshot = null; + try { + snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to take task snapshot", e); + } + if (snapshot != null) { + return new ThumbnailData(snapshot); + } else { + return new ThumbnailData(); + } + } + /** * Removes the outdated snapshot of home task. * 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 3360c967a182..aef83710c17b 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 75465c27724d..f4b429659d8a 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/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 01a75d9e0f16..e47d36f76a14 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,6 +15,8 @@ */ package com.android.keyguard +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -37,7 +39,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 +import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -61,6 +63,7 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import java.util.Locale import java.util.TimeZone @@ -297,7 +300,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { + if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { if (!isKeyguardVisible) { clock?.run { smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -342,9 +345,9 @@ constructor( keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) disposableHandle = parent.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) - if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { + if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) } else { @@ -454,8 +457,9 @@ constructor( @VisibleForTesting internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor.anyStateToAodTransition - .filter { it.transitionState == TransitionState.FINISHED } + keyguardTransitionInteractor.transitionStepsToState(AOD) + .filter { it.transitionState == TransitionState.STARTED } + .filter { it.from != LOCKSCREEN } .collect { handleDoze(1f) } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index f6a0563ebf94..9bddcd79ca49 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -22,10 +22,10 @@ import androidx.core.content.res.ResourcesCompat; import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; -import com.android.systemui.res.R; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; +import com.android.systemui.res.R; import com.android.systemui.shared.clocks.DefaultClockController; import java.io.PrintWriter; @@ -452,6 +452,10 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); + // TODO: b/305022530 + if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) { + mClock.getEvents().onColorPaletteChanged(mContext.getResources()); + } if (changed) { post(() -> updateClockTargetRegions()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 50be97ec1af9..3585feb3442d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -44,8 +44,6 @@ import android.util.AttributeSet; import android.view.WindowInsets; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.TextView; import androidx.annotation.NonNull; @@ -66,18 +64,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; */ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { - private final int mDisappearYTranslation; - - private static final long IME_DISAPPEAR_DURATION_MS = 125; - - // A delay constant to be used in a workaround for the situation where InputMethodManagerService - // is not switched to the new user yet. - // TODO: Remove this by ensuring such a race condition never happens. - private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private Interpolator mLinearOutSlowInInterpolator; - private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; @Nullable private MotionLayout mContainerMotionLayout; private boolean mAlreadyUsingSplitBouncer = false; @@ -93,12 +81,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mDisappearYTranslation = getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.fast_out_linear_in); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 959cf6fb8565..01fc03516edc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -51,7 +51,6 @@ import java.util.List; public class KeyguardPasswordViewController extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { - private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final DevicePostureController mPostureController; private final DevicePostureController.Callback mPostureCallback = posture -> @@ -164,13 +163,6 @@ public class KeyguardPasswordViewController // If there's more than one IME, enable the IME switcher button updateSwitchImeButton(); - - // When we the current user is switching, InputMethodManagerService sometimes has not - // switched internal state yet here. As a quick workaround, we check the keyboard state - // again. - // TODO: Remove this workaround by ensuring such a race condition never happens. - mMainExecutor.executeDelayed( - this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 8717a532b43d..d2d051735643 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 248b7afd7535..b52a36b8199e 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/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 79642bdae1ce..758d1fef6e2e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -54,6 +54,9 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.ClockController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; @@ -102,10 +105,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; - + private boolean mGoneToAodTransitionRunning = false; private DumpManager mDumpManager; private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = @@ -135,6 +139,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, PowerInteractor powerInteractor) { super(keyguardStatusView); @@ -144,12 +149,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mConfigurationController = configurationController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, - logger.getBuffer()); + featureFlags, logger.getBuffer()); mInteractionJankMonitor = interactionJankMonitor; mFeatureFlags = featureFlags; mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; } @Override @@ -199,6 +205,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV dozeTimeTick(); } }, context); + + collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(), + (TransitionStep step) -> { + if (step.getTransitionState() == TransitionState.RUNNING) { + mGoneToAodTransitionRunning = true; + } else { + mGoneToAodTransitionRunning = false; + } + }, context); } public KeyguardStatusView getView() { @@ -266,7 +281,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) { mView.setAlpha(alpha); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3bf148276eab..205c297cebc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -153,7 +153,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -177,6 +176,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -1250,6 +1250,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthFailed() { Assert.isMainThread(); String reason = @@ -1278,6 +1279,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1299,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); try { @@ -1327,6 +1330,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceHelp(int msgId, String helpString) { if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { return; @@ -1344,6 +1348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; @@ -1979,6 +1984,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationAcquired(int acquireInfo) { Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired"); + mLogger.logFingerprintAcquired(acquireInfo); handleFingerprintAcquired(acquireInfo); Trace.endSection(); } @@ -3457,7 +3463,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Deprecated private boolean isUnlockWithFacePossible(int userId) { if (isFaceAuthInteractorEnabled()) { - return getFaceAuthInteractor().canFaceAuthRun(); + return getFaceAuthInteractor() != null + && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled(); } return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7b596328ca13..247606771155 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -25,12 +25,14 @@ import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.util.TimeZone; /** * Callback for general information relevant to lock screen. */ +@WeaklyReferencedCallback public class KeyguardUpdateMonitorCallback { /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index c64ae0106b4a..d524e4a8c408 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,6 +23,8 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; @@ -53,6 +55,7 @@ public class KeyguardVisibilityHelper { private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); + private final FeatureFlags mFeatureFlags; private final LogBuffer mLogBuffer; public KeyguardVisibilityHelper(View view, @@ -60,12 +63,14 @@ public class KeyguardVisibilityHelper { DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, boolean animateYPos, + FeatureFlags featureFlags, LogBuffer logBuffer) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; mScreenOffAnimationController = screenOffAnimationController; mAnimateYPos = animateYPos; + mFeatureFlags = featureFlags; mLogBuffer = logBuffer; } @@ -162,13 +167,17 @@ public class KeyguardVisibilityHelper { animProps, true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { - log("ScreenOff transition"); - mKeyguardViewVisibilityAnimating = true; + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + log("Using GoneToAodTransition"); + mKeyguardViewVisibilityAnimating = false; + } else { + log("ScreenOff transition"); + mKeyguardViewVisibilityAnimating = true; - // Ask the screen off animation controller to animate the keyguard visibility for us - // since it may need to be cancelled due to keyguard lifecycle events. - mScreenOffAnimationController.animateInKeyguard( - mView, mSetVisibleEndRunnable); + // Ask the screen off animation controller to animate the keyguard visibility + // for us since it may need to be cancelled due to keyguard lifecycle events. + mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable); + } } else { log("Direct set Visibility to VISIBLE"); mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 40d0be1173fa..ff6a3d0cc6f0 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -25,7 +25,6 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -105,18 +104,6 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); } - void setImageDrawable(Drawable drawable) { - mLockIcon.setImageDrawable(drawable); - - if (!mUseBackground) return; - - if (drawable == null) { - mBgView.setVisibility(View.INVISIBLE); - } else { - mBgView.setVisibility(View.VISIBLE); - } - } - /** * Whether or not to render the lock icon background. Mainly used for UDPFS. */ @@ -197,6 +184,7 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon = new ImageView(context, attrs); mLockIcon.setId(R.id.lock_icon); mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP); + mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon)); addView(mLockIcon); LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams(); lp.height = MATCH_PARENT; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 83da80f4123a..611283f12984 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -35,7 +35,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricSourceType; import android.os.Process; @@ -120,9 +119,6 @@ public class LockIconViewController implements Dumpable { private boolean mUdfpsEnrolled; private Resources mResources; private Context mContext; - - @NonNull private final AnimatedStateListDrawable mIcon; - @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @NonNull private final VibratorHelper mVibrator; @@ -147,7 +143,6 @@ public class LockIconViewController implements Dumpable { private boolean mCanDismissLockScreen; private int mStatusBarState; private boolean mIsKeyguardShowing; - private Runnable mOnGestureDetectedRunnable; private Runnable mLongPressCancelRunnable; private boolean mUdfpsSupported; @@ -232,9 +227,6 @@ public class LockIconViewController implements Dumpable { mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - - mIcon = (AnimatedStateListDrawable) - resources.getDrawable(R.drawable.super_lock_icon, context.getTheme()); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress); @@ -270,7 +262,6 @@ public class LockIconViewController implements Dumpable { @SuppressLint("ClickableViewAccessibility") public void setLockIconView(LockIconView lockIconView) { mView = lockIconView; - mView.setImageDrawable(mIcon); mView.setAccessibilityDelegate(mAccessibilityDelegate); if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { @@ -492,10 +483,6 @@ public class LockIconViewController implements Dumpable { pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); - pw.println(" mIcon: "); - for (int state : mIcon.getState()) { - pw.print(" " + state); - } pw.println(); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index fe19616cef1d..fa07072b7fe1 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -44,6 +44,7 @@ import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject private const val TAG = "KeyguardUpdateMonitorLog" +private const val FP_LOG_TAG = "KeyguardFingerprintLog" /** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */ class KeyguardUpdateMonitorLogger @@ -157,7 +158,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logFingerprintAuthForWrongUser(authUserId: Int) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { int1 = authUserId }, { "Fingerprint authenticated for wrong user: $int1" } @@ -166,7 +167,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logFingerprintDisabledForUser(userId: Int) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { int1 = userId }, { "Fingerprint disabled by DPM for userId: $int1" } @@ -174,12 +175,17 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { } fun logFingerprintLockoutReset(@LockoutMode mode: Int) { - logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" }) + logBuffer.log( + FP_LOG_TAG, + DEBUG, + { int1 = mode }, + { "handleFingerprintLockoutReset: $int1" } + ) } fun logFingerprintRunningState(fingerprintRunningState: Int) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { int1 = fingerprintRunningState }, { "fingerprintRunningState: $int1" } @@ -188,7 +194,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { int1 = userId @@ -212,7 +218,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { int1 = userId @@ -224,7 +230,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logFingerprintError(msgId: Int, originalErrMsg: String) { logBuffer.log( - TAG, + FP_LOG_TAG, DEBUG, { str1 = originalErrMsg @@ -751,4 +757,25 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { { "userSwitchComplete: $str1, userId: $int1" } ) } + + fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) { + logBuffer.log( + FP_LOG_TAG, + DEBUG, + { + int1 = helpMsgId + str1 = "$helpString" + }, + { "fingerprint help message: $int1, $str1" } + ) + } + + fun logFingerprintAcquired(acquireInfo: Int) { + logBuffer.log( + FP_LOG_TAG, + DEBUG, + { int1 = acquireInfo }, + { "fingerprint acquire message: $int1" } + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index c1f6259d94f7..40f229b7004a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -41,6 +41,8 @@ import android.view.LayoutInflater import android.view.Surface import android.view.View import android.view.View.AccessibilityDelegate +import android.view.View.INVISIBLE +import android.view.View.VISIBLE import android.view.ViewPropertyAnimator import android.view.WindowManager import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION @@ -54,13 +56,13 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.keyguard.KeyguardPINView import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 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.res.R import com.android.systemui.util.boundsOnScreen import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.traceSection @@ -229,6 +231,20 @@ constructor( } } + /** Hide the arrow indicator. */ + fun hideIndicator() { + val lottieAnimationView = + overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? + lottieAnimationView?.visibility = INVISIBLE + } + + /** Show the arrow indicator. */ + fun showIndicator() { + val lottieAnimationView = + overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? + lottieAnimationView?.visibility = VISIBLE + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("requests:") for (requestSource in requests) { @@ -247,6 +263,10 @@ constructor( pw.println(" displayId=${displayInfo.uniqueId}") pw.println(" sensorType=${sensorProps?.sensorType}") pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}") + pw.println("lottieAnimationView:") + pw.println( + " visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}" + ) pw.println("overlayOffsets=$overlayOffsets") pw.println("isReverseDefaultRotation=$isReverseDefaultRotation") @@ -498,5 +518,5 @@ enum class SideFpsUiRequestSource { AUTO_SHOW, /** Pin, pattern or password bouncer */ PRIMARY_BOUNCER, - ALTERNATE_BOUNCER + ALTERNATE_BOUNCER, } 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 a590dccd5318..b9b2fd8875d9 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 f36a3ec89e3f..a317a0684055 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 75ae061f8cce..000000000000 --- 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 000000000000..f85203ea2076 --- /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 000000000000..35f8e3bb461f --- /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 10a3e915fe80..336404c04dbf 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/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt new file mode 100644 index 000000000000..b8e2de404628 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.dagger + +import com.android.systemui.communal.data.repository.CommunalRepositoryModule +import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule +import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule +import dagger.Module + +@Module( + includes = + [ + CommunalRepositoryModule::class, + CommunalTutorialRepositoryModule::class, + CommunalWidgetRepositoryModule::class, + ] +) +class CommunalModule diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt new file mode 100644 index 000000000000..f9c4f29afee9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.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.communal.data.model + +import com.android.systemui.communal.shared.CommunalContentSize + +/** Metadata for the default widgets */ +data class CommunalWidgetMetadata( + /* Widget provider component name */ + val componentName: String, + + /* Defines the order in which the widget will be rendered in the grid. */ + val priority: Int, + + /* Supported sizes */ + val sizes: List<CommunalContentSize> +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e2a7d077a32c..f13b62fbfeb9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -27,13 +27,17 @@ import android.content.pm.PackageManager import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -45,15 +49,20 @@ import kotlinx.coroutines.flow.map interface CommunalWidgetRepository { /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */ val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> + + /** Widgets that are allowed to render in the glanceable hub */ + val communalWidgetAllowlist: List<CommunalWidgetMetadata> } @SysUISingleton class CommunalWidgetRepositoryImpl @Inject constructor( + @Application private val applicationContext: Context, private val appWidgetManager: AppWidgetManager, private val appWidgetHost: AppWidgetHost, broadcastDispatcher: BroadcastDispatcher, + communalRepository: CommunalRepository, private val packageManager: PackageManager, private val userManager: UserManager, private val userTracker: UserTracker, @@ -64,12 +73,18 @@ constructor( const val TAG = "CommunalWidgetRepository" const val WIDGET_LABEL = "Stopwatch" } + override val communalWidgetAllowlist: List<CommunalWidgetMetadata> private val logger = Logger(logBuffer, TAG) // Whether the [AppWidgetHost] is listening for updates. private var isHostListening = false + init { + communalWidgetAllowlist = + if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList() + } + // Widgets that should be rendered in communal mode. private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() @@ -129,6 +144,18 @@ constructor( return@map addWidget(providerInfo) } + private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { + val componentNames = + applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) + return componentNames.mapIndexed { index, name -> + CommunalWidgetMetadata( + componentName = name, + priority = componentNames.size - index, + sizes = listOf(CommunalContentSize.HALF) + ) + } + } + private fun startListening() { if (isHostListening) { return diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt new file mode 100644 index 000000000000..0bd7d86c972d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt @@ -0,0 +1,8 @@ +package com.android.systemui.communal.shared + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { + FULL, + HALF, + THIRD, +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index 3ff1f09cc0f1..d8d1dc0c11ef 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint @@ -28,10 +29,15 @@ import javax.inject.Inject class DefaultCommunalBlueprint @Inject constructor( + defaultCommunalHubSection: DefaultCommunalHubSection, defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection) + override val sections: Set<KeyguardSection> = + setOf( + defaultCommunalHubSection, + defaultCommunalWidgetSection, + ) companion object { const val COMMUNAL = "communal" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt new file mode 100644 index 000000000000..ad02f6280a64 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt @@ -0,0 +1,64 @@ +package com.android.systemui.communal.ui.view.layout.sections + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject + +/** A keyguard section that hosts the communal hub. */ +class DefaultCommunalHubSection +@Inject +constructor( + private val viewModel: CommunalViewModel, +) : KeyguardSection() { + private val communalHubViewId = R.id.communal_hub + + override fun addViews(constraintLayout: ConstraintLayout) { + constraintLayout.addView( + ComposeFacade.createCommunalView( + context = constraintLayout.context, + viewModel = viewModel, + ) + .apply { id = communalHubViewId }, + ) + } + + override fun bindData(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect( + communalHubViewId, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START, + ) + connect( + communalHubViewId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP, + ) + connect( + communalHubViewId, + ConstraintSet.END, + ConstraintSet.PARENT_ID, + ConstraintSet.END, + ) + connect( + communalHubViewId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM, + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(communalHubViewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt new file mode 100644 index 000000000000..ddeb1d67b945 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class CommunalViewModel +@Inject +constructor( + tutorialInteractor: CommunalTutorialInteractor, +) { + /** Whether communal hub should show tutorial content. */ + val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable +} diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 1a6f7e13cf68..9221832b4ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.lifecycle.LifecycleOwner +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene @@ -72,4 +73,10 @@ interface BaseComposeFacade { windowInsets: StateFlow<WindowInsets?>, sceneByKey: Map<SceneKey, Scene>, ): View + + /** Create a [View] to represent [viewModel] on screen. */ + fun createCommunalView( + context: Context, + viewModel: CommunalViewModel, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04a9cae31382..d57f31f91df1 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; @@ -414,6 +415,13 @@ public class FrameworkServicesModule { } @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/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java index ca725c0e39ff..5c38264fbcf6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java @@ -64,6 +64,7 @@ public class GlobalModule { * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated. */ @Provides + @Deprecated public DisplayMetrics provideDisplayMetrics(Context context) { DisplayMetrics displayMetrics = new DisplayMetrics(); context.getDisplay().getMetrics(displayMetrics); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7d4e1a1011db..f0d7592d8940 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -42,6 +42,7 @@ import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.common.ui.data.repository.CommonRepositoryModule; +import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; @@ -170,6 +171,7 @@ import javax.inject.Named; ClipboardOverlayModule.class, ClockRegistryModule.class, CommonRepositoryModule.class, + CommunalModule.class, ConnectivityModule.class, ControlsModule.class, CoroutinesModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index e7f835f7b858..c3aaef76cb2f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -1,5 +1,6 @@ package com.android.systemui.deviceentry +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import dagger.Module @@ -7,6 +8,7 @@ import dagger.Module includes = [ DeviceEntryRepositoryModule::class, + DeviceEntryHapticsRepositoryModule::class, ], ) object DeviceEntryModule diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt new file mode 100644 index 000000000000..1458404446e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt @@ -0,0 +1,72 @@ +/* + * 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.deviceentry.data.repository + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Interface for classes that can access device-entry haptics application state. */ +interface DeviceEntryHapticsRepository { + /** + * Whether a successful biometric haptic has been requested. Has not yet been handled if true. + */ + val successHapticRequest: Flow<Boolean> + + /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ + val errorHapticRequest: Flow<Boolean> + + fun requestSuccessHaptic() + fun handleSuccessHaptic() + fun requestErrorHaptic() + fun handleErrorHaptic() +} + +/** Encapsulates application state for device entry haptics. */ +@SysUISingleton +class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { + private val _successHapticRequest = MutableStateFlow(false) + override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() + + private val _errorHapticRequest = MutableStateFlow(false) + override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() + + override fun requestSuccessHaptic() { + _successHapticRequest.value = true + } + + override fun handleSuccessHaptic() { + _successHapticRequest.value = false + } + + override fun requestErrorHaptic() { + _errorHapticRequest.value = true + } + + override fun handleErrorHaptic() { + _errorHapticRequest.value = false + } +} + +@Module +interface DeviceEntryHapticsRepositoryModule { + @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt new file mode 100644 index 000000000000..53d6f737af8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -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.systemui.deviceentry.domain.interactor + +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Business logic for device entry haptic events. Determines whether the haptic should play. In + * particular, there are extra guards for whether device entry error and successes hatpics should + * play when the physical fingerprint sensor is located on the power button. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryHapticsInteractor +@Inject +constructor( + private val repository: DeviceEntryHapticsRepository, + fingerprintPropertyRepository: FingerprintPropertyRepository, + biometricSettingsRepository: BiometricSettingsRepository, + keyEventInteractor: KeyEventInteractor, + powerInteractor: PowerInteractor, + private val systemClock: SystemClock, + private val logger: BiometricUnlockLogger, +) { + private val powerButtonSideFpsEnrolled = + combineTransform( + fingerprintPropertyRepository.sensorType, + biometricSettingsRepository.isFingerprintEnrolledAndEnabled, + ) { sensorType, enrolledAndEnabled -> + if (sensorType == FingerprintSensorType.POWER_BUTTON) { + emit(enrolledAndEnabled) + } else { + emit(false) + } + } + .distinctUntilChanged() + private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown + private val lastPowerButtonWakeup: Flow<Long> = + powerInteractor.detailedWakefulness + .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } + .map { systemClock.uptimeMillis() } + .onStart { + // If the power button hasn't been pressed, we still want this to evaluate to true: + // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` + emit(recentPowerButtonPressThresholdMs * -1L - 1L) + } + + val playSuccessHaptic: Flow<Boolean> = + repository.successHapticRequest + .filter { it } + .sample( + combine( + powerButtonSideFpsEnrolled, + powerButtonDown, + lastPowerButtonWakeup, + ::Triple + ) + ) + .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + val sideFpsAllowsHaptic = + !powerButtonDown && + systemClock.uptimeMillis() - lastPowerButtonWakeup > + recentPowerButtonPressThresholdMs + val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic + if (!allowHaptic) { + logger.d("Skip success haptic. Recent power button press or button is down.") + handleSuccessHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + val playErrorHaptic: Flow<Boolean> = + repository.errorHapticRequest + .filter { it } + .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) + .map { (sideFpsEnrolled, powerButtonDown) -> + val allowHaptic = !sideFpsEnrolled || !powerButtonDown + if (!allowHaptic) { + logger.d("Skip error haptic. Power button is down.") + handleErrorHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + + fun vibrateSuccess() { + repository.requestSuccessHaptic() + } + + fun vibrateError() { + repository.requestErrorHaptic() + } + + fun handleSuccessHaptic() { + repository.handleSuccessHaptic() + } + + fun handleErrorHaptic() { + repository.handleErrorHaptic() + } + + private val recentPowerButtonPressThresholdMs = 400L +} 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 7510cf6ce04c..d19efbdd8026 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 694695017efd..1c2ff4b1b42c 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 c7f4afc2e1a2..674500c48bc8 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 @@ -256,7 +259,7 @@ object Flags { // TODO(b/290652751): Tracking bug. @JvmField val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = - unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true) + releasedFlag("migrate_split_keyguard_bottom_area") // TODO(b/297037052): Tracking bug. @JvmField @@ -271,7 +274,7 @@ object Flags { /** Migrate the lock icon view to the new keyguard root view. */ // TODO(b/286552209): Tracking bug. - @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true) + @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon") // TODO(b/288276738): Tracking bug. @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard") @@ -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") @@ -413,7 +419,7 @@ object Flags { releasedFlag("incompatible_charging_battery_icon") // TODO(b/293585143): Tracking Bug - val INSTANT_TETHER = unreleasedFlag("instant_tether") + val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true) // TODO(b/294588085): Tracking Bug val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") @@ -473,6 +479,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") + // TODO(b/304506662): Tracking Bug + val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") @@ -494,10 +503,9 @@ object Flags { @Keep @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING = - unreleasedFlag( - name = "record_task_content", + releasedFlag( + name = "enable_record_task_content", namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, - teamfood = true ) // TODO(b/254512674): Tracking Bug @@ -617,7 +625,7 @@ object Flags { /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */ @JvmField - val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot") + val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true) // 1400 - columbus // TODO(b/254512756): Tracking Bug @@ -788,8 +796,7 @@ object Flags { /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField - val ENABLE_CLOCK_KEYGUARD_PRESENTATION = - unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true) + val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") /** Enable the Compose implementation of the PeopleSpaceActivity. */ @JvmField @@ -809,8 +816,7 @@ object Flags { // TODO(b/287205379): Tracking bug @JvmField - val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer", - teamfood = true) + val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer") /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt index 3fe68062f19a..7ccc26c063d3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt @@ -22,11 +22,11 @@ import com.android.systemui.Dependency /** * This class promotes best practices for flag guarding System UI view refactors. * * [isEnabled] allows changing an implementation. - * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and + * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and * ensure that it is not being invoked accidentally in the post-flag refactor. - * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on - * flag-disabled builds, but with a check that should crash eng builds or tests when the - * expectation is violated. + * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when + * invoked on flag-disabled builds, but with a check that should crash eng builds or tests when + * the expectation is violated. * * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a * View class, it's acceptable to ue the [forView] constructor methods, which do not require one, @@ -60,13 +60,13 @@ private constructor( * Example usage: * ``` * public void setController(NotificationShelfController notificationShelfController) { - * mShelfRefactor.assertDisabled(); + * mShelfRefactor.assertInLegacyMode(); * mController = notificationShelfController; * } * ```` */ - fun assertDisabled() = - check(!isEnabled) { "Code path not supported when $flagName is enabled." } + fun assertInLegacyMode() = + check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." } /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -76,18 +76,17 @@ private constructor( * Example usage: * ``` * public void setShelfIcons(NotificationIconContainer icons) { - * if (mShelfRefactor.expectEnabled()) { - * mShelfIcons = icons; - * } + * if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; + * mShelfIcons = icons; * } * ``` */ - fun expectEnabled(): Boolean { + fun isUnexpectedlyInLegacyMode(): Boolean { if (!isEnabled) { - val message = "Code path not supported when $flagName is disabled." + val message = "New code path expects $flagName to be enabled." Log.wtf(TAG, message, Exception(message)) } - return isEnabled + return !isEnabled } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt index 20d99d1e75fb..7b33e11a0c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt @@ -32,6 +32,8 @@ data class SliderHapticFeedbackConfig( @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f, /** Additional time delta to wait between drag texture vibrations */ @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f, + /** Progress threshold beyond which a new drag texture is delivered */ + @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f, /** Number of low ticks in a drag texture composition. This is not expected to change */ val numberOfLowTicks: Int = 5, /** Maximum velocity allowed for vibration scaling. This is not expected to change. */ diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt index e6de156de0c4..f313fb3eef0f 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt @@ -46,6 +46,8 @@ class SliderHapticFeedbackProvider( private val positionAccelerateInterpolator = AccelerateInterpolator(config.progressInterpolatorFactor) private var dragTextureLastTime = clock.elapsedRealtime() + var dragTextureLastProgress = -1f + private set private val lowTickDurationMs = vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0] private var hasVibratedAtLowerBookend = false @@ -91,6 +93,9 @@ class SliderHapticFeedbackProvider( val elapsedSinceLastDrag = currentTime - dragTextureLastTime if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return + val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress) + if (deltaProgress < config.deltaProgressForDragThreshold) return + val velocityInterpolated = velocityAccelerateInterpolator.getInterpolation( min(absoluteVelocity / config.maxVelocityToScale, 1f) @@ -116,11 +121,14 @@ class SliderHapticFeedbackProvider( } vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING) dragTextureLastTime = currentTime + dragTextureLastProgress = normalizedSliderProgress } override fun onHandleAcquiredByTouch() {} - override fun onHandleReleasedFromTouch() {} + override fun onHandleReleasedFromTouch() { + dragTextureLastProgress = -1f + } override fun onLowerBookend() { if (!hasVibratedAtLowerBookend) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fde92b85cac3..0bac40bcbcc1 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/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 86bf368791bc..119ade48d4f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard import android.content.Context import android.view.LayoutInflater import android.view.View +import com.android.internal.jank.InteractionJankMonitor import com.android.keyguard.KeyguardStatusView import com.android.keyguard.KeyguardStatusViewController import com.android.keyguard.LockIconView @@ -27,6 +28,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -43,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import javax.inject.Inject @@ -71,6 +74,9 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, private val shadeInteractor: ShadeInteractor, + private val interactionJankMonitor: InteractionJankMonitor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + private val vibratorHelper: VibratorHelper, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -140,6 +146,9 @@ constructor( keyguardStateController, shadeInteractor, { keyguardStatusViewController!!.getClockController() }, + interactionJankMonitor, + deviceEntryHapticsInteractor, + vibratorHelper, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 081edd152538..019d4283a03c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -38,9 +38,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; -import com.android.systemui.communal.data.repository.CommunalRepositoryModule; -import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule; -import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -96,9 +93,6 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, includes = { - CommunalRepositoryModule.class, - CommunalTutorialRepositoryModule.class, - CommunalWidgetRepositoryModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index e8740a4b24c5..654f2d106206 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -90,7 +90,7 @@ interface BiometricSettingsRepository { * If the current user can use face auth to enter the device. This is true when the user has * face auth enrolled, and is enabled in settings/device policy. */ - val isFaceAuthEnrolledAndEnabled: Flow<Boolean> + val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> /** * If the current user can use face auth to enter the device right now. This is true when @@ -348,10 +348,11 @@ constructor( .and(isFingerprintBiometricAllowed) .stateIn(scope, SharingStarted.Eagerly, false) - override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = + override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> = isFaceAuthenticationEnabled .and(isFaceEnrolled) .and(mobileConnectionsRepository.isAnySimSecure.isFalse()) + .stateIn(scope, SharingStarted.Eagerly, false) override val isFaceAuthCurrentlyAllowed: Flow<Boolean> = isFaceAuthEnrolledAndEnabled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 2f8010620e5e..565962394db1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -22,6 +22,7 @@ import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger +import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -32,6 +33,11 @@ interface KeyguardRepositoryModule { @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository @Binds + @IntoMap + @ClassKey(SideFpsProgressBarViewBinder::class) + fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable + + @Binds fun keyguardSurfaceBehindRepository( impl: KeyguardSurfaceBehindRepositoryImpl ): KeyguardSurfaceBehindRepository diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 38eb730d1498..6e0aa4c7682d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -26,10 +26,11 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import javax.inject.Inject @SysUISingleton class FromAodTransitionInteractor @@ -86,15 +87,19 @@ constructor( } } } - override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR - duration = TRANSITION_DURATION_MS + duration = + when (toState) { + KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + else -> DEFAULT_DURATION + }.inWholeMilliseconds } } companion object { - private const val TRANSITION_DURATION_MS = 500L + val TO_LOCKSCREEN_DURATION = 500.milliseconds + private val DEFAULT_DURATION = 500.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 75aa4b60f7b6..ca882e539a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,9 +29,7 @@ import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -64,29 +62,14 @@ constructor( private fun listenForDreamingToOccluded() { scope.launch { - keyguardInteractor.isDreaming - // Add a slight delay, as dreaming and occluded events will arrive with a small gap - // in time. This prevents a transition to OCCLUSION happening prematurely. - .onEach { delay(50) } - .sample( - combine( - keyguardInteractor.isKeyguardOccluded, - transitionInteractor.startedKeyguardTransitionStep, - ::Pair, - ), - ::toTriple - ) - .collect { (isDreaming, isOccluded, lastStartedTransition) -> + combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair) + .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple) + .collect { (isOccluded, isDreaming, lastStartedTransition) -> if ( isOccluded && !isDreaming && - (lastStartedTransition.to == KeyguardState.DREAMING || - lastStartedTransition.to == KeyguardState.LOCKSCREEN) + lastStartedTransition.to == KeyguardState.DREAMING ) { - // At the moment, checking for LOCKSCREEN state above provides a corrective - // action. There's no great signal to determine when the dream is ending - // and a transition to OCCLUDED is beginning directly. For now, the solution - // is DREAMING->LOCKSCREEN->OCCLUDED startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index ad51e74090d9..c67153a5963d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -114,7 +114,8 @@ constructor( .collect { (isAsleep, lastStartedStep, isAodAvailable) -> if (lastStartedStep.to == KeyguardState.GONE && isAsleep) { startTransitionTo( - if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING + if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING, + resetIfCancelled = true, ) } } @@ -127,6 +128,7 @@ constructor( duration = when (toState) { KeyguardState.DREAMING -> TO_DREAMING_DURATION + KeyguardState.AOD -> TO_AOD_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -134,5 +136,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds + val TO_AOD_DURATION = 1100.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ffa1a4959878..c39a4c9c4172 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -33,6 +33,9 @@ import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -40,9 +43,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import java.util.UUID -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromLockscreenTransitionInteractor @@ -318,16 +318,9 @@ constructor( private fun listenForLockscreenToOccluded() { scope.launch { keyguardInteractor.isKeyguardOccluded - .sample( - combine( - transitionInteractor.startedKeyguardState, - keyguardInteractor.isDreaming, - ::Pair - ), - ::toTriple - ) - .collect { (isOccluded, keyguardState, isDreaming) -> - if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + .sample(transitionInteractor.startedKeyguardState, ::Pair) + .collect { (isOccluded, keyguardState) -> + if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { startTransitionTo(KeyguardState.OCCLUDED) } } @@ -362,6 +355,7 @@ constructor( when (toState) { KeyguardState.DREAMING -> TO_DREAMING_DURATION KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION + KeyguardState.AOD -> TO_AOD_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -371,5 +365,6 @@ constructor( private val DEFAULT_DURATION = 400.milliseconds val TO_DREAMING_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds + val TO_AOD_DURATION = 500.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 89aca7631934..85b0f4fb864b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -41,6 +41,9 @@ interface KeyguardFaceAuthInteractor { /** Whether face auth is in lock out state. */ fun isLockedOut(): Boolean + /** Whether face auth is enrolled and enabled for the current user */ + fun isFaceAuthEnabledAndEnrolled(): Boolean + /** * Register listener for use from code that cannot use [authenticationStatus] or * [detectionStatus] diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 6e19fdbefab5..b953b4879a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -39,7 +39,6 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel -import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R @@ -49,6 +48,8 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -64,8 +65,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart -import javax.inject.Inject -import javax.inject.Provider /** * Encapsulates business-logic related to the keyguard but not to a more specific part within it. @@ -148,9 +147,7 @@ constructor( .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel -> isDreaming && isDozeOff(dozeTransitionModel.to) } - .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> - isAbleToDream && isAwake - } + .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake } .flatMapLatest { isAbleToDream -> flow { delay(50) @@ -217,11 +214,8 @@ constructor( val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation /** Notifies when a new configuration is set */ - val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange - - /** Represents the current state of the KeyguardRootView visibility */ - val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> = - repository.keyguardRootViewVisibility + val configurationChange: Flow<Unit> = + configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) } /** The position of the keyguard clock. */ val clockPosition: Flow<Position> = repository.clockPosition @@ -235,12 +229,17 @@ constructor( R.dimen.keyguard_translate_distance_on_swipe_up ) shadeRepository.shadeModel.map { - // On swipe up, translate the keyguard to reveal the bouncer - MathUtils.lerp( - translationDistance, - 0, - Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount) - ) + if (it.expansionAmount == 0f) { + // Reset the translation value + 0f + } else { + // On swipe up, translate the keyguard to reveal the bouncer + MathUtils.lerp( + translationDistance, + 0, + Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount) + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index f38bb2b519e7..fbadde63a6b9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -43,6 +43,7 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt override fun isLockedOut(): Boolean = false override fun isEnabled() = false + override fun isFaceAuthEnabledAndEnrolled(): Boolean = false override fun registerListener(listener: FaceAuthenticationListener) {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 797dec2c9625..2641846251cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus @@ -81,6 +82,7 @@ constructor( private val facePropertyRepository: FacePropertyRepository, private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, private val powerInteractor: PowerInteractor, + private val biometricSettingsRepository: BiometricSettingsRepository, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -149,7 +151,10 @@ constructor( .onEach { if (it) { faceAuthenticationLogger.faceLockedOut("Fingerprint locked out") - repository.setLockedOut(true) + // We don't care about this if face auth is not enabled. + if (isFaceAuthEnabledAndEnrolled()) { + repository.setLockedOut(true) + } } } .launchIn(applicationScope) @@ -263,6 +268,9 @@ constructor( } } + override fun isFaceAuthEnabledAndEnrolled(): Boolean = + biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value + private fun observeFaceAuthStateUpdates() { authenticationStatus .onEach { authStatusUpdate -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt index ae18681a5b92..3c143fe1a68a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -16,6 +16,8 @@ package com.android.systemui.keyguard.shared.model +import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD +import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START import android.hardware.fingerprint.FingerprintManager import android.os.SystemClock.elapsedRealtime @@ -39,7 +41,12 @@ data class HelpFingerprintAuthenticationStatus( /** Fingerprint acquired message. */ data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : - FingerprintAuthenticationStatus() + FingerprintAuthenticationStatus() { + + val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START + + val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD +} /** Fingerprint authentication failed message. */ object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index ac4ad39a38a5..4d5c503d1c4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -17,24 +17,30 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.DrawableRes +import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup +import android.view.ViewGroup.OnHierarchyChangeListener import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD +import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -42,14 +48,13 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ @ExperimentalCoroutinesApi object KeyguardRootViewBinder { - private var onLayoutChangeListener: OnLayoutChange? = null - @JvmStatic fun bind( view: ViewGroup, @@ -60,7 +65,16 @@ object KeyguardRootViewBinder { keyguardStateController: KeyguardStateController, shadeInteractor: ShadeInteractor, clockControllerProvider: Provider<ClockController>?, + interactionJankMonitor: InteractionJankMonitor?, + deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, + vibratorHelper: VibratorHelper?, ): DisposableHandle { + var onLayoutChangeListener: OnLayoutChange? = null + val childViews = mutableMapOf<Int, View?>() + val statusViewId = R.id.keyguard_status_view + val burnInLayerId = R.id.burn_in_layer + val aodNotificationIconContainerId = R.id.aod_notification_icon_container + val largeClockId = R.id.lockscreen_clock_view_large val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -86,36 +100,74 @@ object KeyguardRootViewBinder { if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { launch { + viewModel.burnInLayerVisibility.collect { visibility -> + childViews[burnInLayerId]?.visibility = visibility + // Reset alpha only for the icons, as they currently have their + // own animator + childViews[aodNotificationIconContainerId]?.alpha = 0f + } + } + + launch { + viewModel.burnInLayerAlpha.collect { alpha -> + childViews[statusViewId]?.alpha = alpha + childViews[aodNotificationIconContainerId]?.alpha = alpha + } + } + + launch { + viewModel.lockscreenStateAlpha.collect { alpha -> + childViews[statusViewId]?.alpha = alpha + } + } + + launch { viewModel.translationY.collect { y -> - val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) - burnInLayer.translationY = y + childViews[burnInLayerId]?.translationY = y } } - } - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { launch { viewModel.translationX.collect { x -> - val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) - burnInLayer.translationX = x + childViews[burnInLayerId]?.translationX = x } } - } - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { launch { viewModel.scale.collect { (scale, scaleClockOnly) -> if (scaleClockOnly) { - val largeClock = - view.findViewById<View?>(R.id.lockscreen_clock_view_large) - largeClock?.let { + childViews[largeClockId]?.let { it.scaleX = scale it.scaleY = scale } } else { - val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) - burnInLayer.scaleX = scale - burnInLayer.scaleY = scale + childViews[burnInLayerId]?.scaleX = scale + childViews[burnInLayerId]?.scaleY = scale + } + } + } + + interactionJankMonitor?.let { jankMonitor -> + launch { + viewModel.goneToAodTransition.collect { + when (it.transitionState) { + TransitionState.STARTED -> { + val clockId = + clockControllerProvider?.get()?.config?.id + ?: MISSING_CLOCK_ID + val builder = + InteractionJankMonitor.Configuration.Builder + .withView(CUJ_SCREEN_OFF_SHOW_AOD, view) + .setTag(clockId) + + jankMonitor.begin(builder) + } + TransitionState.CANCELED -> + jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + TransitionState.FINISHED -> + jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) + TransitionState.RUNNING -> Unit + } } } } @@ -131,42 +183,42 @@ object KeyguardRootViewBinder { } } } - } - repeatOnLifecycle(Lifecycle.State.STARTED) { - if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { - viewModel.keyguardRootViewVisibilityState.collect { visibilityState -> - view.animate().cancel() - val goingToFullShade = visibilityState.goingToFullShade - val statusBarState = visibilityState.statusBarState - val isOcclusionTransitionRunning = - visibilityState.occlusionTransitionRunning - if (goingToFullShade) { - view - .animate() - .alpha(0f) - .setStartDelay( - keyguardStateController.keyguardFadingAwayDelay + deviceEntryHapticsInteractor.playSuccessHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, ) - .setDuration( - keyguardStateController.shortenedFadingAwayDuration + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::success") + } + deviceEntryHapticsInteractor.handleSuccessHaptic() + } + } + + launch { + deviceEntryHapticsInteractor.playErrorHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.REJECT, ) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction { view.visibility = View.GONE } - .start() - } else if ( - statusBarState == StatusBarState.KEYGUARD || - statusBarState == StatusBarState.SHADE_LOCKED - ) { - view.visibility = View.VISIBLE - if (!isOcclusionTransitionRunning) { - view.alpha = 1f + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::error") } - } else { - view.visibility = View.GONE + deviceEntryHapticsInteractor.handleErrorHaptic() } - } } } } @@ -176,10 +228,26 @@ object KeyguardRootViewBinder { onLayoutChangeListener = OnLayoutChange(viewModel) view.addOnLayoutChangeListener(onLayoutChangeListener) + // Views will be added or removed after the call to bind(). This is needed to avoid many + // calls to findViewById + view.setOnHierarchyChangeListener( + object : OnHierarchyChangeListener { + override fun onChildViewAdded(parent: View, child: View) { + childViews.put(child.id, child) + } + + override fun onChildViewRemoved(parent: View, child: View) { + childViews.remove(child.id) + } + } + ) + return object : DisposableHandle { override fun dispose() { disposableHandle.dispose() view.removeOnLayoutChangeListener(onLayoutChangeListener) + view.setOnHierarchyChangeListener(null) + childViews.clear() } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt index f3586bae1a9e..2feaa2e81c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch /** @@ -46,8 +47,8 @@ class PreviewKeyguardBlueprintViewBinder { constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel, finishedAddViewCallback: () -> Unit - ) { - constraintLayout.repeatWhenAttached { + ): DisposableHandle { + return constraintLayout.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.blueprint.collect { blueprint -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt new file mode 100644 index 000000000000..1acea5cfc579 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt @@ -0,0 +1,74 @@ +/* + * 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.keyguard.ui.binder + +import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.SideFpsController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.ui.view.SideFpsProgressBar +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.util.kotlin.Quint +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@SysUISingleton +class SideFpsProgressBarViewBinder +@Inject +constructor( + private val viewModel: SideFpsProgressBarViewModel, + private val view: SideFpsProgressBar, + @Application private val applicationScope: CoroutineScope, + private val sfpsController: dagger.Lazy<SideFpsController>, +) : CoreStartable { + + override fun start() { + applicationScope.launch { + viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled -> + if (enabled) { + launch { + combine( + viewModel.isVisible, + viewModel.sensorLocation, + viewModel.shouldRotate90Degrees, + viewModel.isFingerprintAuthRunning, + viewModel.sensorWidth, + ::Quint + ) + .collectLatest { + (visible, location, shouldRotate, fpDetectRunning, sensorWidth) -> + view.updateView(visible, location, shouldRotate, sensorWidth) + // We have to hide the SFPS indicator as the progress bar will + // be shown at the same location + if (visible) { + sfpsController.get().hideIndicator() + } else if (fpDetectRunning) { + sfpsController.get().showIndicator() + } + } + } + launch { viewModel.progress.collectLatest { view.setProgress(it) } } + } else { + view.hideOverlay() + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index c6d8ec7789f2..692984a90a14 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -27,14 +27,15 @@ import android.hardware.display.DisplayManager import android.os.Bundle import android.os.Handler import android.os.IBinder +import android.view.ContextThemeWrapper import android.view.Display import android.view.Display.DEFAULT_DISPLAY +import android.view.DisplayInfo import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible @@ -45,6 +46,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder @@ -113,6 +115,7 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val keyguardStateController: KeyguardStateController, private val shadeInteractor: ShadeInteractor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -129,7 +132,7 @@ constructor( bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS) private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) - private val display: Display = displayManager.getDisplay(displayId) + private val display: Display? = displayManager.getDisplay(displayId) private var host: SurfaceControlViewHost @@ -179,7 +182,11 @@ constructor( fun render() { mainHandler.post { - val previewContext = context.createDisplayContext(display) + val previewContext = + display?.let { + ContextThemeWrapper(context.createDisplayContext(it), context.getTheme()) + } + ?: context val rootView = FrameLayout(previewContext) @@ -189,16 +196,18 @@ constructor( setUpBottomArea(rootView) } - val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null) - val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java) + var displayInfo: DisplayInfo? = null + display?.let { + displayInfo = DisplayInfo() + it.getDisplayInfo(displayInfo) + } rootView.measure( View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width() - ?: windowManager.currentWindowMetrics.bounds.width(), + displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(), View.MeasureSpec.EXACTLY ), View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height() + displayInfo?.logicalHeight ?: windowManager.currentWindowMetrics.bounds.height(), View.MeasureSpec.EXACTLY ), @@ -320,7 +329,7 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { - val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() } + val keyguardRootView = KeyguardRootView(previewContext, null) disposables.add( KeyguardRootViewBinder.bind( keyguardRootView, @@ -331,6 +340,9 @@ constructor( keyguardStateController, shadeInteractor, null, // clock provider only needed for burn in + null, // jank monitor not required for preview mode + null, // device entry haptics not required for preview mode + null, // device entry haptics not required for preview mode ) ) rootView.addView( @@ -340,26 +352,29 @@ constructor( FrameLayout.LayoutParams.MATCH_PARENT, ), ) - PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) { - if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - setupShortcuts(keyguardRootView) - } - setUpUdfps(previewContext, rootView) - - if (!shouldHideClock) { - setUpClock(previewContext, rootView) - KeyguardPreviewClockViewBinder.bind( - largeClockHostView, - smallClockHostView, - clockViewModel, - ) - } - setUpSmartspace(previewContext, rootView) - smartSpaceView?.let { - KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) + disposables.add( + PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) { + if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { + setupShortcuts(keyguardRootView) + } + setUpUdfps(previewContext, rootView) + + if (!shouldHideClock) { + setUpClock(previewContext, rootView) + KeyguardPreviewClockViewBinder.bind( + largeClockHostView, + smallClockHostView, + clockViewModel, + ) + } + + setUpSmartspace(previewContext, rootView) + smartSpaceView?.let { + KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) + } } - } + ) } private fun setupShortcuts(keyguardRootView: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt new file mode 100644 index 000000000000..f7ab1ee77582 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt @@ -0,0 +1,114 @@ +/* + * 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.keyguard.ui.view + +import android.graphics.PixelFormat +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.ProgressBar +import com.android.systemui.biometrics.Utils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject + +private const val TAG = "SideFpsProgressBar" + +const val progressBarHeight = 100 + +@SysUISingleton +class SideFpsProgressBar +@Inject +constructor( + private val layoutInflater: LayoutInflater, + private val windowManager: WindowManager, +) { + private var progressBarWidth = 200 + fun updateView( + visible: Boolean, + location: Point, + shouldRotate90Degrees: Boolean, + progressBarWidth: Int + ) { + if (visible) { + this.progressBarWidth = progressBarWidth + createAndShowOverlay(location, shouldRotate90Degrees) + } else { + hideOverlay() + } + } + + fun hideOverlay() { + overlayView = null + } + + private val overlayViewParams = + WindowManager.LayoutParams( + progressBarHeight, + progressBarWidth, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, + PixelFormat.TRANSPARENT + ) + .apply { + title = TAG + fitInsetsTypes = 0 // overrides default, avoiding status bars during layout + gravity = Gravity.TOP or Gravity.LEFT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = + WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or + WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION + } + + private var overlayView: View? = null + set(value) { + field?.let { oldView -> windowManager.removeView(oldView) } + field = value + field?.let { newView -> windowManager.addView(newView, overlayViewParams) } + } + + private fun createAndShowOverlay( + fingerprintSensorLocation: Point, + shouldRotate90Degrees: Boolean + ) { + if (overlayView == null) { + overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false) + } + overlayViewParams.x = fingerprintSensorLocation.x + overlayViewParams.y = fingerprintSensorLocation.y + if (shouldRotate90Degrees) { + overlayView?.rotation = 270.0f + overlayViewParams.width = progressBarHeight + overlayViewParams.height = progressBarWidth + } else { + overlayView?.rotation = 0.0f + overlayViewParams.width = progressBarWidth + overlayViewParams.height = progressBarHeight + } + windowManager.updateViewLayout(overlayView, overlayViewParams) + } + + fun setProgress(value: Float) { + overlayView + ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar) + ?.setProgress((value * 100).toInt(), false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt index 9371d4e2d465..342a440d972b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt @@ -50,11 +50,8 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) { if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - val view = - LayoutInflater.from(constraintLayout.context) - .inflate(R.layout.ambient_indication, constraintLayout, false) - - constraintLayout.addView(view) + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.ambient_indication, constraintLayout, true) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..024707ad2885 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -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.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume. + */ +@SysUISingleton +class AodToLockscreenTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.aodToLockscreenTransition, + ) + + /** Ensure alpha is set to be visible */ + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 500.milliseconds, + onStart = { 1f }, + onStep = { 1f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt new file mode 100644 index 000000000000..601dbccb1de1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -0,0 +1,63 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class GoneToAodTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_AOD_DURATION, + transitionFlow = interactor.goneToAodTransition, + ) + + /** y-translation from the top of the screen for AOD */ + fun enterFromTopTranslationY(translatePx: Int): Flow<Float> { + return transitionAnimation.createFlow( + startTime = 600.milliseconds, + duration = 500.milliseconds, + onStart = { translatePx }, + onStep = { translatePx + it * -translatePx }, + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED_DECELERATE, + ) + } + + /** alpha animation upon entering AOD */ + val enterFromTopAnimationAlpha: Flow<Float> = + transitionAnimation.createFlow( + startTime = 600.milliseconds, + duration = 500.milliseconds, + onStart = { 0f }, + onStep = { it }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 89835fecd95c..1f98082c4065 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -17,14 +17,19 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.content.Context import android.util.MathUtils +import android.view.View.VISIBLE import com.android.app.animation.Interpolators import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.plugins.ClockController +import com.android.systemui.res.R import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,17 +37,23 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart @OptIn(ExperimentalCoroutinesApi::class) class KeyguardRootViewModel @Inject constructor( + private val context: Context, private val keyguardInteractor: KeyguardInteractor, private val burnInInteractor: BurnInInteractor, + private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { data class PreviewMode(val isInPreviewMode: Boolean = false) @@ -56,9 +67,12 @@ constructor( public var clockControllerProvider: Provider<ClockController>? = null - /** Represents the current state of the KeyguardRootView visibility */ - val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> = - keyguardInteractor.keyguardRootViewVisibilityState + val burnInLayerVisibility: Flow<Int> = + keyguardTransitionInteractor.startedKeyguardState + .filter { it == AOD || it == LOCKSCREEN } + .map { VISIBLE } + + val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = @@ -70,9 +84,14 @@ constructor( } } - private val burnIn: Flow<BurnInModel> = - combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn - -> + private fun burnIn(): Flow<BurnInModel> { + val dozingAmount: Flow<Float> = + merge( + keyguardTransitionInteractor.goneToAodTransition.map { it.value }, + keyguardTransitionInteractor.dozeAmountTransition.map { it.value }, + ) + + return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn -> val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) val useScaleOnly = clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false @@ -91,13 +110,61 @@ constructor( ) } } + } + + /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ + val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha + + /** For elements that appear and move during the animation -> AOD */ + val burnInLayerAlpha: Flow<Float> = + previewMode.flatMapLatest { + if (it.isInPreviewMode) { + flowOf(1f) + } else { + goneToAodTransitionViewModel.enterFromTopAnimationAlpha + } + } val translationY: Flow<Float> = - merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() }) + previewMode.flatMapLatest { + if (it.isInPreviewMode) { + flowOf(0f) + } else { + keyguardInteractor.configurationChange.flatMapLatest { _ -> + val enterFromTopAmount = + context.resources.getDimensionPixelSize( + R.dimen.keyguard_enter_from_top_translation_y + ) + combine( + keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, + burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) }, + goneToAodTransitionViewModel + .enterFromTopTranslationY(enterFromTopAmount) + .onStart { emit(0f) }, + ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY -> + // All 3 values need to be combined for a smooth translation + keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY + } + } + } + } - val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() } + val translationX: Flow<Float> = + previewMode.flatMapLatest { + if (it.isInPreviewMode) { + flowOf(0f) + } else { + burnIn().map { it.translationX.toFloat() } + } + } - val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) } + val scale: Flow<Pair<Float, Boolean>> = + previewMode.flatMapLatest { previewMode -> + burnIn().map { + val scale = if (previewMode.isInPreviewMode) 1f else it.scale + Pair(scale, it.scaleClockOnly) + } + } /** * Puts this view-model in "preview mode", which means it's being used for UI that is rendering @@ -105,11 +172,14 @@ constructor( * lock screen. */ fun enablePreviewMode() { - val newPreviewMode = PreviewMode(true) - previewMode.value = newPreviewMode + previewMode.value = PreviewMode(true) } fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) { + // Notifications should not be visible in preview mode + if (previewMode.value.isInPreviewMode) { + return + } keyguardInteractor.sharedNotificationContainerPosition.value = SharedNotificationContainerPosition(top, bottom) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt new file mode 100644 index 000000000000..2c3b431715a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt @@ -0,0 +1,138 @@ +/* + * 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.keyguard.ui.viewmodel + +import android.animation.ValueAnimator +import android.graphics.Point +import androidx.core.animation.doOnEnd +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.isDefaultOrientation +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +@SysUISingleton +class SideFpsProgressBarViewModel +@Inject +constructor( + private val fpAuthRepository: DeviceEntryFingerprintAuthRepository, + private val sfpsSensorInteractor: SideFpsSensorInteractor, + displayStateInteractor: DisplayStateInteractor, + @Application private val applicationScope: CoroutineScope, +) { + private val _progress = MutableStateFlow(0.0f) + private val _visible = MutableStateFlow(false) + private var _animator: ValueAnimator? = null + + private fun onFingerprintCaptureCompleted() { + _visible.value = false + _progress.value = 0.0f + } + + val isVisible: Flow<Boolean> = _visible.asStateFlow() + + val progress: Flow<Float> = _progress.asStateFlow() + + val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width } + + val sensorLocation: Flow<Point> = + sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) } + + val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning + + val shouldRotate90Degrees: Flow<Boolean> = + combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair) + .map { (rotation, sensorLocation) -> + if (rotation.isDefaultOrientation()) { + sensorLocation.isSensorVerticalInDefaultOrientation + } else { + !sensorLocation.isSensorVerticalInDefaultOrientation + } + } + + val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = + sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication + + init { + applicationScope.launch { + combine( + sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication, + sfpsSensorInteractor.authenticationDuration, + ::Pair + ) + .collectLatest { (enabled, authDuration) -> + if (!enabled) return@collectLatest + + launch { + fpAuthRepository.authenticationStatus.collectLatest { authStatus -> + when (authStatus) { + is AcquiredFingerprintAuthenticationStatus -> { + if (authStatus.fingerprintCaptureStarted) { + + _visible.value = true + _animator?.cancel() + _animator = + ValueAnimator.ofFloat(0.0f, 1.0f) + .setDuration(authDuration) + .apply { + addUpdateListener { + _progress.value = it.animatedValue as Float + } + addListener( + doOnEnd { + if (_progress.value == 0.0f) { + _visible.value = false + } + } + ) + } + _animator?.start() + } else if (authStatus.fingerprintCaptureCompleted) { + onFingerprintCaptureCompleted() + } else { + // Abandoned FP Auth attempt + _animator?.reverse() + } + } + is ErrorFingerprintAuthenticationStatus -> + onFingerprintCaptureCompleted() + is FailFingerprintAuthenticationStatus -> + onFingerprintCaptureCompleted() + is SuccessFingerprintAuthenticationStatus -> + onFingerprintCaptureCompleted() + else -> Unit + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index a1291a4d6b0f..724241d8d41f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.content.Context import android.os.SystemProperties import android.util.Log +import com.android.internal.annotations.KeepForWeakReference import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main @@ -82,6 +83,8 @@ constructor( private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var reactivatedKey: String? = null + // Ensure the field (and associated reference) isn't removed during optimization. + @KeepForWeakReference private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 1fe93ed5503d..1db31ae4e050 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager +import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable -import com.android.systemui.res.R 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.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, - dumpManager: DumpManager + dumpManager: DumpManager, + private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") + println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } @@ -348,16 +354,16 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice - val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + val routingSession = + controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device - val enabled = device != null && (controller == null || route != null) - val name = - if (controller == null || route != null) { - route?.name?.toString() ?: device?.name - } else { - null - } + val enabled = device != null && (controller == null || routingSession != null) + + val name = getDeviceName(device, routingSession) + if (DEBUG) { + Log.d(TAG, "new device name $name") + } current = MediaDeviceData( enabled, @@ -369,6 +375,57 @@ constructor( } } + /** Return a display name for the current device / route, or null if not possible */ + private fun getDeviceName( + device: MediaDevice?, + routingSession: RoutingSessionInfo?, + ): String? { + val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + + if (DEBUG) { + Log.d( + TAG, + "device is $device, controller $controller," + + " routingSession ${routingSession?.name}" + + " or ${selectedRoutes?.firstOrNull()?.name}" + ) + } + + if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { + if (controller == null || routingSession != null) { + return routingSession?.name?.toString() ?: device?.name + } + return null + } + + if (controller == null) { + // In resume state, we don't have a controller - just use the device name + return device?.name + } + + if (routingSession == null) { + // This happens when casting from apps that do not support MediaRouter2 + // The output switcher can't show anything useful here, so set to null + return null + } + + // If this is a user route (app / cast provided), use the provided name + if (!routingSession.isSystemSession) { + return routingSession.name?.toString() ?: device?.name + } + + selectedRoutes?.firstOrNull()?.let { + if (device is PhoneMediaDevice) { + // Get the (localized) name for this phone device + return PhoneMediaDevice.getSystemRouteNameFromType(context, it) + } else { + // If it's another type of device (in practice, Bluetooth), use the route name + return it.name.toString() + } + } + return null + } + private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager 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 000000000000..a53f0f11c380 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -0,0 +1,109 @@ +/* + * 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.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED +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 notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) { + notifyToServer( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, + sessionCreationSource + ) + } + + /** + * Request to log that the permission request moved to the given state. + * + * Should not be used for the initialization state, since that should use {@link + * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the + * sessionCreationSource. + */ + fun notifyPermissionProgress(state: Int) { + // TODO validate state is valid + notifyToServer(state, SessionCreationSource.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: SessionCreationSource) { + Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") + try { + service.notifyPermissionRequestStateChange( + Process.myUid(), + state, + sessionCreationSource.toMetricsConstant() + ) + } catch (e: RemoteException) { + Log.e( + TAG, + "Error notifying server of permission flow state $state from source " + + "$sessionCreationSource", + e + ) + } + } + + companion object { + const val TAG = "MediaProjectionMetricsLogger" + } +} + +enum class SessionCreationSource { + APP, + CAST, + SYSTEM_UI_SCREEN_RECORDER, + UNKNOWN; + + fun toMetricsConstant(): Int = + when (this) { + APP -> METRICS_CREATION_SOURCE_APP + CAST -> METRICS_CREATION_SOURCE_CAST + SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER + UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index b5d3e913cadb..0bbcfd9de24c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -87,14 +87,15 @@ class MediaProjectionAppSelectorActivity( override fun getLayoutResource() = R.layout.media_projection_app_selector - public override fun onCreate(bundle: Bundle?) { + public override fun onCreate(savedInstanceState: Bundle?) { lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) component = componentFactory.create( hostUserHandle = hostUserHandle, callingPackage = callingPackage, view = this, - resultHandler = this + resultHandler = this, + isFirstStart = savedInstanceState == null ) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } @@ -113,7 +114,7 @@ class MediaProjectionAppSelectorActivity( reviewGrantedConsentRequired = intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false) - super.onCreate(bundle) + super.onCreate(savedInstanceState) controller.init() // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 2217509167ef..8c6f307c84d6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -146,6 +146,9 @@ interface MediaProjectionAppSelectorComponent { @BindsInstance @MediaProjectionAppSelector callingPackage: String?, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, + // Whether the app selector is starting for the first time. False when it is re-starting + // due to a config change. + @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean, ): MediaProjectionAppSelectorComponent } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index e61650fbb163..69132d3662d4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -18,12 +18,19 @@ package com.android.systemui.mediaprojection.appselector import android.content.ComponentName import android.os.UserHandle +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @MediaProjectionAppSelectorScope @@ -36,16 +43,30 @@ constructor( @HostUserHandle private val hostUserHandle: UserHandle, @MediaProjectionAppSelector private val scope: CoroutineScope, @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, - @MediaProjectionAppSelector private val callerPackageName: String? + @MediaProjectionAppSelector private val callerPackageName: String?, + private val thumbnailLoader: RecentTaskThumbnailLoader, + @MediaProjectionAppSelector private val isFirstStart: Boolean, + private val logger: MediaProjectionMetricsLogger, ) { fun init() { + // Only log during the first start of the app selector. + // Don't log when the app selector restarts due to a config change. + if (isFirstStart) { + logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + scope.launch { val recentTasks = recentTaskListProvider.loadRecentTasks() val tasks = recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks() + // Thumbnails are not fresh for the foreground task(s). They are only refreshed at + // launch, going to home, or going to overview. + // For this reason, we need to refresh them here. + refreshForegroundTaskThumbnails(tasks) + view.bind(tasks) } } @@ -54,6 +75,16 @@ constructor( scope.cancel() } + private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) { + coroutineScope { + val thumbnails: List<Deferred<ThumbnailData?>> = + tasks + .filter { it.isForegroundTask } + .map { async { thumbnailLoader.captureThumbnail(it.taskId) } } + thumbnails.forEach { thumbnail -> thumbnail.await() } + } + } + /** Removes all recent tasks that should be blocked according to the policy */ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter { devicePolicyResolver.isScreenCaptureAllowed( diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index 41e22860d0ad..e9b458271ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -22,8 +22,10 @@ import android.content.ComponentName data class RecentTask( val taskId: Int, + val displayId: Int, @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, - @ColorInt val colorBackground: Int? + @ColorInt val colorBackground: Int?, + val isForegroundTask: Boolean, ) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 01398cf81314..730aa620690a 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -48,17 +48,24 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() - - rawRecentTasks + val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + // Note: the returned task list is from the most-recent to least-recent order. + // The last foreground task is at index 1, because at index 0 will be our app selector. + val foregroundGroup = groupedTasks.elementAtOrNull(1) + val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId + val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId + val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2) + groupedTasks .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) } .map { RecentTask( it.taskId, + it.displayId, it.userId, it.topActivity, it.baseIntent?.component, - it.taskDescription?.backgroundColor + it.taskDescription?.backgroundColor, + isForegroundTask = it.taskId in foregroundTaskIds ) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt index 47faaed10302..ccf272cbd3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.withContext interface RecentTaskThumbnailLoader { suspend fun loadThumbnail(taskId: Int): ThumbnailData? + + suspend fun captureThumbnail(taskId: Int): ThumbnailData? } class ActivityTaskManagerThumbnailLoader @@ -36,8 +38,13 @@ constructor( override suspend fun loadThumbnail(taskId: Int): ThumbnailData? = withContext(coroutineDispatcher) { - val thumbnailData = - activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false) - if (thumbnailData.thumbnail == null) null else thumbnailData + activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf { + it.thumbnail != null + } + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? = + withContext(coroutineDispatcher) { + activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null } } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index fd1a683dc78f..ba837dba5354 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -130,10 +130,10 @@ constructor( view.width, view.height ) - activityOptions.setPendingIntentBackgroundActivityStartMode( + activityOptions.pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED - ) activityOptions.launchCookie = launchCookie + activityOptions.launchDisplayId = task.displayId activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) resultHandler.returnSelectedApp(launchCookie) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index d08d0400f354..fa418fc8b98b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -53,7 +53,9 @@ import android.view.Window; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; @@ -74,6 +76,7 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; private final StatusBarManager mStatusBarManager; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private String mPackageName; private int mUid; @@ -90,15 +93,17 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, - StatusBarManager statusBarManager) { + StatusBarManager statusBarManager, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; } @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( @@ -133,6 +138,10 @@ public class MediaProjectionPermissionActivity extends Activity try { if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) { + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.APP); + } final IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName, mReviewGrantedConsentRequired); @@ -231,6 +240,13 @@ public class MediaProjectionPermissionActivity extends Activity mDialog = dialogBuilder.create(); } + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + appName == null + ? SessionCreationSource.CAST + : SessionCreationSource.APP); + } + setUpDialog(mDialog); mDialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index 2f10ad3e6486..b9bafd4926fa 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -89,15 +89,15 @@ class MediaProjectionPermissionDialog( } return listOf( ScreenShareOption( - mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, - warningText = entireScreenWarningText - ), - ScreenShareOption( mode = SINGLE_APP, spinnerText = R.string.screen_share_permission_dialog_option_single_app, warningText = singleAppWarningText, spinnerDisabledText = singleAppDisabledText, + ), + ScreenShareOption( + mode = ENTIRE_SCREEN, + spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, + warningText = entireScreenWarningText ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt index 37e8d9f26ee3..9bd57832c4df 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt @@ -23,8 +23,8 @@ import kotlin.annotation.Retention @IntDef(ENTIRE_SCREEN, SINGLE_APP) annotation class ScreenShareMode -const val ENTIRE_SCREEN = 0 -const val SINGLE_APP = 1 +const val SINGLE_APP = 0 +const val ENTIRE_SCREEN = 1 class ScreenShareOption( @ScreenShareMode val mode: Int, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e093854..c77f3f49a77c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -231,7 +231,6 @@ internal constructor( animation.removeEndListener(this) if (!canceled) { - // The delay between finishing this animation and starting the runnable val delay = max(0, runnableDelay - elapsedTimeSinceEntry) @@ -461,7 +460,6 @@ internal constructor( } private fun handleMoveEvent(event: MotionEvent) { - val x = event.x val y = event.y @@ -927,17 +925,7 @@ internal constructor( GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation updateRestingArrowDimens() - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE - ) - } else { - vibratorHelper.cancel() - mainHandler.postDelayed(10L) { - vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) - } - } + performActivatedHapticFeedback() val popVelocity = if (previousState == GestureState.INACTIVE) { POP_ON_INACTIVE_TO_ACTIVE_VELOCITY @@ -958,25 +946,24 @@ internal constructor( mView.popOffEdge(POP_ON_INACTIVE_VELOCITY) - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE - ) - } else { - vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) - } + performDeactivatedHapticFeedback() updateRestingArrowDimens() } GestureState.FLUNG -> { + // Typically a vibration is only played while transitioning to ACTIVE. However there + // are instances where a fling to trigger back occurs while not in that state. + // (e.g. A fling is detected before crossing the trigger threshold.) + if (previousState != GestureState.ACTIVE) { + performActivatedHapticFeedback() + } mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_VELOCITY) } - updateRestingArrowDimens() mainHandler.postDelayed( onEndSetCommittedStateListener.runnable, MIN_DURATION_FLING_ANIMATION ) + updateRestingArrowDimens() } GestureState.COMMITTED -> { // In most cases, animating between states is handled via `updateRestingArrowDimens` @@ -1011,6 +998,31 @@ internal constructor( } } + private fun performDeactivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE + ) + } else { + vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) + } + } + + private fun performActivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE + ) + } else { + vibratorHelper.cancel() + mainHandler.postDelayed(10L) { + vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) + } + } + } + private fun convertVelocityToAnimationFactor( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 733383e344b8..58c4f0d029c7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -96,6 +96,44 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +/** Variables and functions that is related to Emoji. */ +class EmojiHelper { + static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; + + // This regex can be used to match Unicode emoji characters and character sequences. It's from + // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor + // changes to fit our needs. It should be updated once new emoji categories are added. + // + // Emoji categories that can be matched by this regex: + // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode + // scalars. + // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. + // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches + // them. + // - Emoji Presentation. Those are characters which can normally be drawn as either text or as + // Emoji. "\p{Emoji}\x{FE0F}" matches them. + // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. + // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. + // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by + // the jointer "0x200D". + // + // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use + // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji + // regex. + private static final String UNICODE_EMOJI_REGEX = + "\\p{RI}\\p{RI}|" + + "(" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "|[\\p{Emoji}&&\\p{So}]" + + ")" + + "(" + + "\\x{200D}" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "?)*"; + + static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); +} + /** Functions that help creating the People tile layouts. */ public class PeopleTileViewHelper { /** Turns on debugging information about People Space. */ @@ -125,8 +163,6 @@ public class PeopleTileViewHelper { private static final int MESSAGES_COUNT_OVERFLOW = 6; - private static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; - private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -134,39 +170,6 @@ public class PeopleTileViewHelper { static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n"; - // This regex can be used to match Unicode emoji characters and character sequences. It's from - // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor - // changes to fit our needs. It should be updated once new emoji categories are added. - // - // Emoji categories that can be matched by this regex: - // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode - // scalars. - // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. - // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches - // them. - // - Emoji Presentation. Those are characters which can normally be drawn as either text or as - // Emoji. "\p{Emoji}\x{FE0F}" matches them. - // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. - // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. - // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by - // the jointer "0x200D". - // - // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use - // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji - // regex. - private static final String UNICODE_EMOJI_REGEX = - "\\p{RI}\\p{RI}|" - + "(" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "|[\\p{Emoji}&&\\p{So}]" - + ")" - + "(" - + "\\x{200D}" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "?)*"; - - private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); - public static final String EMPTY_STRING = ""; private int mMediumVerticalPadding; @@ -831,7 +834,7 @@ public class PeopleTileViewHelper { if (status.getActivity() == ACTIVITY_BIRTHDAY || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) { - setEmojiBackground(views, EMOJI_CAKE); + setEmojiBackground(views, EmojiHelper.EMOJI_CAKE); } Icon statusIcon = status.getIcon(); @@ -1072,7 +1075,7 @@ public class PeopleTileViewHelper { /** Returns emoji if {@code message} has two of the same emoji in sequence. */ @VisibleForTesting CharSequence getDoubleEmoji(CharSequence message) { - Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message); + Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message); // Stores the start and end indices of each matched emoji. List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>(); // Stores each emoji text. diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index d949a2a0afe5..67d390d4f10d 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.asIndenting +import com.android.systemui.util.annotations.WeaklyReferencedCallback import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.withIncreasedIndent import java.io.PrintWriter @@ -144,6 +145,7 @@ class PrivacyConfig @Inject constructor( ipw.flush() } + @WeaklyReferencedCallback interface Callback { fun onFlagMicCameraChanged(flag: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 5a9c0a1893f8..f8e01590d368 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -23,7 +23,10 @@ import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Resources.ID_NULL +import android.graphics.Color +import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.os.Trace import android.service.quicksettings.Tile @@ -44,7 +47,6 @@ import android.widget.TextView import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils -import com.android.systemui.res.R import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.plugins.qs.QSIconView @@ -53,6 +55,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH +import com.android.systemui.res.R import java.util.Objects private const val TAG = "QSTileViewImpl" @@ -67,6 +70,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val LABEL_NAME = "label" private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" + private const val OVERLAY_NAME = "overlay" const val UNAVAILABLE_ALPHA = 0.3f @VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_" @@ -97,6 +101,13 @@ open class QSTileViewImpl @JvmOverloads constructor( private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) + private val overlayColorActive = Utils.applyAlpha( + /* alpha= */ 0.11f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)) + private val overlayColorInactive = Utils.applyAlpha( + /* alpha= */ 0.08f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = @@ -123,8 +134,13 @@ open class QSTileViewImpl @JvmOverloads constructor( protected var showRippleEffect = true private lateinit var ripple: RippleDrawable - private lateinit var colorBackgroundDrawable: Drawable - private var paintColor: Int = 0 + private lateinit var backgroundDrawable: LayerDrawable + private lateinit var backgroundBaseDrawable: Drawable + private lateinit var backgroundOverlayDrawable: Drawable + + private var backgroundColor: Int = 0 + private var backgroundOverlayColor: Int = 0 + private val singleAnimator: ValueAnimator = ValueAnimator().apply { setDuration(QS_ANIM_LENGTH) addUpdateListener { animation -> @@ -134,7 +150,8 @@ open class QSTileViewImpl @JvmOverloads constructor( animation.getAnimatedValue(BACKGROUND_NAME) as Int, animation.getAnimatedValue(LABEL_NAME) as Int, animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int, - animation.getAnimatedValue(CHEVRON_NAME) as Int + animation.getAnimatedValue(CHEVRON_NAME) as Int, + animation.getAnimatedValue(OVERLAY_NAME) as Int, ) } } @@ -263,7 +280,12 @@ open class QSTileViewImpl @JvmOverloads constructor( fun createTileBackground(): Drawable { ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable - colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable + backgroundBaseDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base) + backgroundOverlayDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay) + backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC) return ripple } @@ -343,10 +365,10 @@ open class QSTileViewImpl @JvmOverloads constructor( ripple.also { // In case that the colorBackgroundDrawable was used as the background, make sure // it has the correct callback instead of null - colorBackgroundDrawable.callback = it + backgroundDrawable.callback = it } } else { - colorBackgroundDrawable + backgroundDrawable } } @@ -512,7 +534,7 @@ open class QSTileViewImpl @JvmOverloads constructor( singleAnimator.setValues( colorValuesHolder( BACKGROUND_NAME, - paintColor, + backgroundColor, getBackgroundColorForState(state.state, state.disabledByPolicy) ), colorValuesHolder( @@ -529,6 +551,11 @@ open class QSTileViewImpl @JvmOverloads constructor( CHEVRON_NAME, chevronView.imageTintList?.defaultColor ?: 0, getChevronColorForState(state.state, state.disabledByPolicy) + ), + colorValuesHolder( + OVERLAY_NAME, + backgroundOverlayColor, + getOverlayColorForState(state.state) ) ) singleAnimator.start() @@ -537,7 +564,8 @@ open class QSTileViewImpl @JvmOverloads constructor( getBackgroundColorForState(state.state, state.disabledByPolicy), getLabelColorForState(state.state, state.disabledByPolicy), getSecondaryLabelColorForState(state.state, state.disabledByPolicy), - getChevronColorForState(state.state, state.disabledByPolicy) + getChevronColorForState(state.state, state.disabledByPolicy), + getOverlayColorForState(state.state) ) } } @@ -555,17 +583,19 @@ open class QSTileViewImpl @JvmOverloads constructor( backgroundColor: Int, labelColor: Int, secondaryLabelColor: Int, - chevronColor: Int + chevronColor: Int, + overlayColor: Int, ) { setColor(backgroundColor) setLabelColor(labelColor) setSecondaryLabelColor(secondaryLabelColor) setChevronColor(chevronColor) + setOverlayColor(overlayColor) } private fun setColor(color: Int) { - colorBackgroundDrawable.mutate().setTint(color) - paintColor = color + backgroundBaseDrawable.mutate().setTint(color) + backgroundColor = color } private fun setLabelColor(color: Int) { @@ -580,6 +610,11 @@ open class QSTileViewImpl @JvmOverloads constructor( chevronView.imageTintList = ColorStateList.valueOf(color) } + private fun setOverlayColor(overlayColor: Int) { + backgroundOverlayDrawable.setTint(overlayColor) + backgroundOverlayColor = overlayColor + } + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { if (state.sideViewCustomDrawable != null) { customDrawableView.setImageDrawable(state.sideViewCustomDrawable) @@ -654,9 +689,17 @@ open class QSTileViewImpl @JvmOverloads constructor( private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int = getSecondaryLabelColorForState(state, disabledByPolicy) + private fun getOverlayColorForState(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> overlayColorActive + Tile.STATE_INACTIVE -> overlayColorInactive + else -> Color.TRANSPARENT + } + } + @VisibleForTesting internal fun getCurrentColors(): List<Int> = listOf( - paintColor, + backgroundColor, label.currentTextColor, secondaryLabel.currentTextColor, chevronView.imageTintList?.defaultColor ?: 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index d862f563c8f9..18d2f306c247 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -39,7 +39,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -53,6 +52,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; @@ -198,6 +198,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } state.expandedAccessibilityClassName = Switch.class.getName(); + state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } /** 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 5c6e9028545a..a69820868838 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/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 7a0c087caacf..f469c6b78e87 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -38,6 +38,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -45,13 +47,13 @@ import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; +import dagger.Lazy; + import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Helper class to initiate a screen recording */ @@ -71,6 +73,7 @@ public class RecordingController private final FeatureFlags mFlags; private final UserContextProvider mUserContextProvider; private final UserTracker mUserTracker; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -115,7 +118,8 @@ public class RecordingController FeatureFlags flags, UserContextProvider userContextProvider, Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, - UserTracker userTracker) { + UserTracker userTracker, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mMainExecutor = mainExecutor; mContext = context; mFlags = flags; @@ -123,6 +127,7 @@ public class RecordingController mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; mUserTracker = userTracker; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -149,6 +154,9 @@ public class RecordingController return new ScreenCaptureDisabledDialog(mContext); } + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this, activityStarter, mUserContextProvider, onStartRecordingClicked) diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index a1d5d98ba9e9..ade93b1a913e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -186,14 +186,14 @@ class ScreenRecordPermissionDialog( private fun createOptionList(): List<ScreenShareOption> { return listOf( ScreenShareOption( - ENTIRE_SCREEN, - R.string.screen_share_permission_dialog_option_entire_screen, - R.string.screenrecord_permission_dialog_warning_entire_screen - ), - ScreenShareOption( SINGLE_APP, R.string.screen_share_permission_dialog_option_single_app, R.string.screenrecord_permission_dialog_warning_single_app + ), + ScreenShareOption( + ENTIRE_SCREEN, + R.string.screen_share_permission_dialog_option_entire_screen, + R.string.screenrecord_permission_dialog_warning_entire_screen ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 9325e18abce7..00d480a76355 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; @@ -44,10 +45,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.screenshot.CropView.CropBoundary; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.android.systemui.settings.UserTracker; @@ -421,13 +422,15 @@ public class LongScreenshotActivity extends Activity { Log.e(TAG, "failed to export", e); return; } + Uri exported = ContentProvider.getUriWithoutUserId(result.uri); + Log.e(TAG, action + " uri=" + exported); switch (action) { case EDIT: - doEdit(result.uri); + doEdit(exported); break; case SHARE: - doShare(result.uri); + doShare(exported); break; case SAVE: // Nothing more to do diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index 51540673b9e2..c34fd42e2154 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -20,11 +20,10 @@ import android.util.Log import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import java.util.function.Consumer import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** Processes a screenshot request sent from [ScreenshotHelper]. */ interface ScreenshotRequestProcessor { @@ -36,16 +35,15 @@ interface ScreenshotRequestProcessor { suspend fun process(screenshot: ScreenshotData): ScreenshotData } -/** - * Implementation of [ScreenshotRequestProcessor] - */ +/** Implementation of [ScreenshotRequestProcessor] */ @SysUISingleton -class RequestProcessor @Inject constructor( - private val capture: ImageCapture, - private val policy: ScreenshotPolicy, - private val flags: FeatureFlags, - /** For the Java Async version, to invoke the callback. */ - @Application private val mainScope: CoroutineScope +class RequestProcessor +@Inject +constructor( + private val capture: ImageCapture, + private val policy: ScreenshotPolicy, + /** For the Java Async version, to invoke the callback. */ + @Application private val mainScope: CoroutineScope ) : ScreenshotRequestProcessor { override suspend fun process(screenshot: ScreenshotData): ScreenshotData { @@ -67,8 +65,9 @@ class RequestProcessor @Inject constructor( result.userHandle = info.user if (policy.isManagedProfile(info.user.identifier)) { - val image = capture.captureTask(info.taskId) - ?: error("Task snapshot returned a null Bitmap!") + val image = + capture.captureTask(info.taskId) + ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!") // Provide the task snapshot as the screenshot result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE @@ -97,3 +96,6 @@ class RequestProcessor @Inject constructor( } private const val TAG = "RequestProcessor" + +/** Exception thrown by [RequestProcessor] if something goes wrong. */ +class RequestProcessorException(message: String) : IllegalStateException(message) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4b3bd0b44bc9..21a08a9a4980 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 @@ -318,7 +325,7 @@ public class ScreenshotController { Context context, FeatureFlags flags, ScreenshotSmartActions screenshotSmartActions, - ScreenshotNotificationsController screenshotNotificationsController, + ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, ScrollCaptureClient scrollCaptureClient, UiEventLogger uiEventLogger, ImageExporter imageExporter, @@ -335,10 +342,11 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId + @Assisted int displayId, + @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; - mNotificationsController = screenshotNotificationsController; + mNotificationsController = screenshotNotificationsControllerFactory.create(displayId); mScrollCaptureClient = scrollCaptureClient; mUiEventLogger = uiEventLogger; mImageExporter = imageExporter; @@ -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/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java deleted file mode 100644 index 4344fd1a7567..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static android.content.Context.NOTIFICATION_SERVICE; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.UserHandle; -import android.util.DisplayMetrics; -import android.view.WindowManager; - -import com.android.internal.messages.nano.SystemMessageProto; -import com.android.systemui.res.R; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.util.NotificationChannels; - -import javax.inject.Inject; - -/** - * Convenience class to handle showing and hiding notifications while taking a screenshot. - */ -public class ScreenshotNotificationsController { - private static final String TAG = "ScreenshotNotificationManager"; - - private final Context mContext; - private final Resources mResources; - private final NotificationManager mNotificationManager; - - @Inject - ScreenshotNotificationsController(Context context, WindowManager windowManager) { - mContext = context; - mResources = context.getResources(); - mNotificationManager = - (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - - DisplayMetrics displayMetrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getRealMetrics(displayMetrics); - } - - /** - * Sends a notification that the screenshot capture has failed. - */ - public void notifyScreenshotError(int msgResId) { - Resources res = mContext.getResources(); - String errorMsg = res.getString(msgResId); - - // Repurpose the existing notification to notify the user of the error - Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS) - .setTicker(res.getString(R.string.screenshot_failed_title)) - .setContentTitle(res.getString(R.string.screenshot_failed_title)) - .setContentText(errorMsg) - .setSmallIcon(R.drawable.stat_notify_image_error) - .setWhen(System.currentTimeMillis()) - .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen - .setCategory(Notification.CATEGORY_ERROR) - .setAutoCancel(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - final DevicePolicyManager dpm = - (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - final Intent intent = - dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); - if (intent != null) { - final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); - b.setContentIntent(pendingIntent); - } - - SystemUIApplication.overrideNotificationAppName(mContext, b, true); - - Notification n = new Notification.BigTextStyle(b) - .bigText(errorMsg) - .build(); - mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt new file mode 100644 index 000000000000..d874eb68460b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.admin.DevicePolicyManager +import android.content.Context +import android.os.UserHandle +import android.view.Display +import com.android.internal.R +import com.android.internal.messages.nano.SystemMessageProto +import com.android.systemui.SystemUIApplication +import com.android.systemui.util.NotificationChannels +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** Convenience class to handle showing and hiding notifications while taking a screenshot. */ +class ScreenshotNotificationsController +@AssistedInject +internal constructor( + @Assisted private val displayId: Int, + private val context: Context, + private val notificationManager: NotificationManager, + private val devicePolicyManager: DevicePolicyManager, +) { + private val res = context.resources + + /** + * Sends a notification that the screenshot capture has failed. + * + * Errors for the non-default display are shown in a unique separate notification. + */ + fun notifyScreenshotError(msgResId: Int) { + val displayErrorString = + if (displayId != Display.DEFAULT_DISPLAY) { + " ($externalDisplayString)" + } else { + "" + } + val errorMsg = res.getString(msgResId) + displayErrorString + + // Repurpose the existing notification or create a new one + val builder = + Notification.Builder(context, NotificationChannels.ALERTS) + .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title)) + .setContentTitle( + res.getString(com.android.systemui.res.R.string.screenshot_failed_title) + ) + .setContentText(errorMsg) + .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error) + .setWhen(System.currentTimeMillis()) + .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen + .setCategory(Notification.CATEGORY_ERROR) + .setAutoCancel(true) + .setColor(context.getColor(R.color.system_notification_accent_color)) + val intent = + devicePolicyManager.createAdminSupportIntent( + DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE + ) + if (intent != null) { + val pendingIntent = + PendingIntent.getActivityAsUser( + context, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE, + null, + UserHandle.CURRENT + ) + builder.setContentIntent(pendingIntent) + } + SystemUIApplication.overrideNotificationAppName(context, builder, true) + val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build() + // A different id for external displays to keep the 2 error notifications separated. + val id = + if (displayId == Display.DEFAULT_DISPLAY) { + SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT + } else { + SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY + } + notificationManager.notify(id, notification) + } + + private val externalDisplayString: String + get() = + res.getString( + com.android.systemui.res.R.string.screenshot_failed_external_display_indication + ) + + /** Factory for [ScreenshotNotificationsController]. */ + @AssistedFactory + interface Factory { + fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java index 070fb1eef99a..049799e96c53 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java @@ -16,25 +16,29 @@ package com.android.systemui.screenshot; +import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.view.WindowManager; +import android.view.Display; import com.android.systemui.res.R; /** - * Performs a number of miscellaneous, non-system-critical actions - * after the system has finished booting. + * Receives errors related to screenshot. */ public class ScreenshotServiceErrorReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { // Show a message that we've failed to save the image to disk - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - ScreenshotNotificationsController controller = - new ScreenshotNotificationsController(context, wm); + NotificationManager notificationManager = context.getSystemService( + NotificationManager.class); + DevicePolicyManager devicePolicyManager = context.getSystemService( + DevicePolicyManager.class); + ScreenshotNotificationsController controller = new ScreenshotNotificationsController( + Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager); controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 03c3f7a8ddf3..015828438375 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -10,6 +10,8 @@ import com.android.internal.util.ScreenshotRequest import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.res.R +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import java.util.function.Consumer import javax.inject.Inject @@ -34,7 +36,8 @@ constructor( displayRepository: DisplayRepository, @Application private val mainScope: CoroutineScope, private val screenshotRequestProcessor: ScreenshotRequestProcessor, - private val uiEventLogger: UiEventLogger + private val uiEventLogger: UiEventLogger, + private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory, ) { private lateinit var displays: StateFlow<Set<Display>> @@ -44,6 +47,7 @@ constructor( } private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() + private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>() /** * Executes the [ScreenshotRequest]. @@ -58,40 +62,68 @@ constructor( ) { val displayIds = getDisplaysToScreenshot(screenshotRequest.type) val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) - screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData -> + displayIds.forEach { displayId: Int -> dispatchToController( - screenshotData = screenshotData, + rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId), onSaved = - if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> }, - callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId) + if (displayId == Display.DEFAULT_DISPLAY) { + onSaved + } else { _ -> }, + callback = resultCallbackWrapper.createCallbackForId(displayId) ) } } - /** Creates a [ScreenshotData] for each display. */ - private suspend fun ScreenshotRequest.oneForEachDisplay( - displayIds: List<Int> - ): List<ScreenshotData> { - return displayIds - .map { displayId -> ScreenshotData.fromRequest(this, displayId) } - .map { screenshotData: ScreenshotData -> - screenshotRequestProcessor.process(screenshotData) - } - } - - private fun dispatchToController( - screenshotData: ScreenshotData, + /** All logging should be triggered only by this method. */ + private suspend fun dispatchToController( + rawScreenshotData: ScreenshotData, onSaved: (Uri) -> Unit, callback: RequestCallback ) { + // Let's wait before logging "screenshot requested", as we should log the processed + // ScreenshotData. + val screenshotData = + try { + screenshotRequestProcessor.process(rawScreenshotData) + } catch (e: RequestProcessorException) { + Log.e(TAG, "Failed to process screenshot request!", e) + logScreenshotRequested(rawScreenshotData) + onFailedScreenshotRequest(rawScreenshotData, callback) + return + } + + logScreenshotRequested(screenshotData) + Log.d(TAG, "Screenshot request: $screenshotData") + try { + getScreenshotController(screenshotData.displayId) + .handleScreenshot(screenshotData, onSaved, callback) + } catch (e: IllegalStateException) { + Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e) + onFailedScreenshotRequest(screenshotData, callback) + return // After a failure log, nothing else should run. + } + } + + /** + * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED] + * event. + */ + private fun logScreenshotRequested(screenshotData: ScreenshotData) { uiEventLogger.log( ScreenshotEvent.getScreenshotSource(screenshotData.source), 0, screenshotData.packageNameString ) - Log.d(TAG, "Screenshot request: $screenshotData") - getScreenshotController(screenshotData.displayId) - .handleScreenshot(screenshotData, onSaved, callback) + } + + private fun onFailedScreenshotRequest( + screenshotData: ScreenshotData, + callback: RequestCallback + ) { + uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString) + getNotificationController(screenshotData.displayId) + .notifyScreenshotError(R.string.screenshot_failed_to_capture_text) + callback.reportError() } private fun getDisplaysToScreenshot(requestType: Int): List<Int> { @@ -135,7 +167,15 @@ constructor( } private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) } + return screenshotControllers.computeIfAbsent(id) { + screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + } + } + + private fun getNotificationController(id: Int): ScreenshotNotificationsController { + return notificationControllers.computeIfAbsent(id) { + screenshotNotificationControllerFactory.create(id) + } } /** 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 0be2265bf8fa..0991c9a326c8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -113,7 +113,8 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory, UserManager userManager, DevicePolicyManager devicePolicyManager, - UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController, + UiEventLogger uiEventLogger, + ScreenshotNotificationsController.Factory notificationsControllerFactory, Context context, @Background Executor bgExecutor, FeatureFlags featureFlags, RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) { if (DEBUG_SERVICE) { @@ -123,7 +124,7 @@ public class TakeScreenshotService extends Service { mUserManager = userManager; mDevicePolicyManager = devicePolicyManager; mUiEventLogger = uiEventLogger; - mNotificationsController = notificationsController; + mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY); mContext = context; mBgExecutor = bgExecutor; mFeatureFlags = featureFlags; @@ -132,7 +133,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); } } @@ -245,15 +247,17 @@ public class TakeScreenshotService extends Service { Log.d(TAG, "Processing screenshot data"); - ScreenshotData screenshotData = ScreenshotData.fromRequest( - request, Display.DEFAULT_DISPLAY); + if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { + mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback); + return; + } + // TODO(b/295143676): Delete the following after the flag is released. try { - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback); - } else { - mProcessor.processAsync(screenshotData, (data) -> - dispatchToController(data, onSaved, callback)); - } + ScreenshotData screenshotData = ScreenshotData.fromRequest( + request, Display.DEFAULT_DISPLAY); + mProcessor.processAsync(screenshotData, (data) -> + dispatchToController(data, onSaved, callback)); + } catch (IllegalStateException e) { Log.e(TAG, "Failed to process screenshot request!", e); logFailedRequest(request); @@ -263,6 +267,7 @@ public class TakeScreenshotService extends Service { } } + // TODO(b/295143676): Delete this. private void dispatchToController(ScreenshotData screenshot, Consumer<Uri> uriConsumer, RequestCallback callback) { mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index bd592c9daff6..cf1fbe3685e3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -16,6 +16,8 @@ package com.android.systemui.settings +import com.android.systemui.util.annotations.WeaklyReferencedCallback + import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle @@ -64,6 +66,7 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** * Callback for notifying of changes. */ + @WeaklyReferencedCallback interface Callback { /** diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 6783afa3eb9d..1ecb127f0c92 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; import android.app.Activity; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.view.Gravity; @@ -35,8 +36,8 @@ import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -74,21 +75,26 @@ public class BrightnessDialog extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setWindowAttributes(); + setContentView(R.layout.brightness_mirror_container); + setBrightnessDialogViewAttributes(); + } + private void setWindowAttributes() { final Window window = getWindow(); - window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + window.setGravity(Gravity.TOP | Gravity.LEFT); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); // Calling this creates the decor View, so setLayout takes proper effect // (see Dialog#onWindowAttributesChanged) window.getDecorView(); - window.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + window.setLayout(WRAP_CONTENT, WRAP_CONTENT); getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); + } - setContentView(R.layout.brightness_mirror_container); + void setBrightnessDialogViewAttributes() { FrameLayout frame = findViewById(R.id.brightness_mirror_container); // The brightness mirror container is INVISIBLE by default. frame.setVisibility(View.VISIBLE); @@ -97,6 +103,14 @@ public class BrightnessDialog extends Activity { getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); lp.leftMargin = horizontalMargin; lp.rightMargin = horizontalMargin; + + int verticalMargin = + getResources().getDimensionPixelSize( + R.dimen.notification_guts_option_vertical_padding); + + lp.topMargin = verticalMargin; + lp.bottomMargin = verticalMargin; + frame.setLayoutParams(lp); Rect bounds = new Rect(); frame.addOnLayoutChangeListener( @@ -113,6 +127,20 @@ public class BrightnessDialog extends Activity { frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); mBrightnessController = mBrightnessControllerFactory.create(controller); + + Configuration configuration = getResources().getConfiguration(); + int orientation = configuration.orientation; + + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2 + - lp.leftMargin * 2; + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() + - lp.leftMargin * 2; + } + + frame.setLayoutParams(lp); + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index ba0cf08150f6..8dc97c0f797c 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) { @@ -1446,9 +1446,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mBarState); } - if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - setKeyguardVisibility(mBarState, false); - } else { + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { setKeyguardBottomAreaVisibility(mBarState, false); } @@ -2358,14 +2356,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) { - mKeyguardInteractor.setKeyguardRootVisibility( - statusBarState, - goingToFullShade, - mIsOcclusionTransitionRunning - ); - } - @Deprecated private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) { mKeyguardBottomArea.animate().cancel(); @@ -4443,11 +4433,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status // view where it'll be on AOD, so we can animate it in. - mKeyguardStatusViewController.updatePosition( - mClockPositionResult.clockX, - mClockPositionResult.clockYFullyDozing, - mClockPositionResult.clockScale, - false /* animate */); + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + mKeyguardStatusViewController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.clockYFullyDozing, + mClockPositionResult.clockScale, + false /* animate */); + } } mKeyguardStatusViewController.setKeyguardStatusViewVisibility( @@ -4456,9 +4448,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump goingToFullShade, mBarState); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - setKeyguardVisibility(statusBarState, goingToFullShade); - } else { + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); } @@ -4562,7 +4552,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump public void showAodUi() { setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); - mStatusBarStateListener.onStateChanged(KEYGUARD); + + if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + mStatusBarStateController.setState(KEYGUARD); + } else { + mStatusBarStateListener.onStateChanged(KEYGUARD); + } mStatusBarStateListener.onDozeAmountChanged(1f, 1f); setExpandedFraction(1f); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 25bd8e7446bc..cb95b25ece80 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/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt deleted file mode 100644 index 17b4e3baef13..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Point -import android.graphics.Rect -import android.renderscript.Allocation -import android.renderscript.Element -import android.renderscript.RenderScript -import android.renderscript.ScriptIntrinsicBlur -import android.util.Log -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.MediaNotificationProcessor -import javax.inject.Inject - -private const val TAG = "MediaArtworkProcessor" -private const val COLOR_ALPHA = (255 * 0.7f).toInt() -private const val BLUR_RADIUS = 25f -private const val DOWNSAMPLE = 6 - -@SysUISingleton -class MediaArtworkProcessor @Inject constructor() { - - private val mTmpSize = Point() - private var mArtworkCache: Bitmap? = null - - fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { - if (mArtworkCache != null) { - return mArtworkCache - } - val renderScript = RenderScript.create(context) - val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) - var input: Allocation? = null - var output: Allocation? = null - var inBitmap: Bitmap? = null - try { - @Suppress("DEPRECATION") - context.display?.getSize(mTmpSize) - val rect = Rect(0, 0, artwork.width, artwork.height) - MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) - inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), - true /* filter */) - // Render script blurs only support ARGB_8888, we need a conversion if we got a - // different bitmap config. - if (inBitmap.config != Bitmap.Config.ARGB_8888) { - val oldIn = inBitmap - inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) - oldIn.recycle() - } - val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0, - Bitmap.Config.ARGB_8888) - - input = Allocation.createFromBitmap(renderScript, inBitmap, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) - output = Allocation.createFromBitmap(renderScript, outBitmap) - - blur.setRadius(BLUR_RADIUS) - blur.setInput(input) - blur.forEach(output) - output.copyTo(outBitmap) - - val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) - - val canvas = Canvas(outBitmap) - canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) - return outBitmap - } catch (ex: IllegalArgumentException) { - Log.e(TAG, "error while processing artwork", ex) - return null - } finally { - input?.destroy() - output?.destroy() - blur.destroy() - inBitmap?.recycle() - } - } - - fun clearCache() { - mArtworkCache?.recycle() - mArtworkCache = null - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2147510bbde5..f32f1c2dcd25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,9 +16,16 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_NULL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; @@ -30,8 +37,10 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -41,14 +50,18 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.recents.OverviewProxyService; @@ -63,7 +76,9 @@ import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -84,6 +99,11 @@ public class NotificationLockscreenUserManagerImpl implements private final SecureSettings mSecureSettings; private final Object mLock = new Object(); + private static final Uri SHOW_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS); + private static final Uri SHOW_PRIVATE_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; private final DevicePolicyManager mDevicePolicyManager; @@ -91,6 +111,23 @@ public class NotificationLockscreenUserManagerImpl implements private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); + + // The variables between mUsersDpcAllowingNotifications and + // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread + // and read on the main thread. Because the pipeline needs these values, adding locks would + // introduce too much jank. This means that some pipeline runs could get incorrect values, that + // would be fixed on the next pipeline run. We think this will be rare since a pipeline run + // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the + // pipeline frequently enough that it should be corrected by the next time it matters for the + // user. + private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray(); + private boolean mKeyguardAllowingNotifications = true; + private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray(); private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray(); private final UserManager mUserManager; @@ -99,24 +136,39 @@ public class NotificationLockscreenUserManagerImpl implements private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; + private final FeatureFlagsClassic mFeatureFlags; private boolean mShowLockscreenNotifications; private LockPatternUtils mLockPatternUtils; protected KeyguardManager mKeyguardManager; private int mState = StatusBarState.SHADE; private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners = new ListenerSet<>(); + private final Collection<Uri> mLockScreenUris = new ArrayList<>(); + protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && - isCurrentProfile(getSendingUserId())) { - mUsersAllowingPrivateNotifications.clear(); - updateLockscreenNotificationSetting(); - // TODO(b/231976036): Consolidate pipeline invalidations related to this event - // notifyNotificationStateChanged(); + if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = updateDpcSettings(getSendingUserId()); + if (mCurrentUserId == getSendingUserId()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } else { + if (isCurrentProfile(getSendingUserId())) { + mUsersAllowingPrivateNotifications.clear(); + updateLockscreenNotificationSetting(); + // TODO(b/231976036): Consolidate pipeline invalidations related to this + // event + // notifyNotificationStateChanged(); + } + } } } }; @@ -136,6 +188,14 @@ public class NotificationLockscreenUserManagerImpl implements updateCurrentProfilesCache(); break; case Intent.ACTION_USER_ADDED: + updateCurrentProfilesCache(); + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mBackgroundHandler.post(() -> { + initValuesForUser(userId); + }); + } + break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: updateCurrentProfilesCache(); @@ -193,6 +253,8 @@ public class NotificationLockscreenUserManagerImpl implements protected final Context mContext; private final Handler mMainHandler; + private final Handler mBackgroundHandler; + private final Executor mBackgroundExecutor; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); @@ -214,13 +276,18 @@ public class NotificationLockscreenUserManagerImpl implements KeyguardManager keyguardManager, StatusBarStateController statusBarStateController, @Main Handler mainHandler, + @Background Handler backgroundHandler, + @Background Executor backgroundExecutor, DeviceProvisionedController deviceProvisionedController, KeyguardStateController keyguardStateController, SecureSettings secureSettings, DumpManager dumpManager, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, + FeatureFlagsClassic featureFlags) { mContext = context; mMainHandler = mainHandler; + mBackgroundHandler = backgroundHandler; + mBackgroundExecutor = backgroundExecutor; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; mUserTracker = userTracker; @@ -236,6 +303,10 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; + mFeatureFlags = featureFlags; + + mLockScreenUris.add(SHOW_LOCKSCREEN); + mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN); dumpManager.registerDumpable(this); } @@ -243,16 +314,53 @@ public class NotificationLockscreenUserManagerImpl implements public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; - mLockscreenSettingsObserver = new ContentObserver(mMainHandler) { + mLockscreenSettingsObserver = new ContentObserver( + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundHandler + : mMainHandler) { + @Override - public void onChange(boolean selfChange) { - // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or - // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... - mUsersAllowingPrivateNotifications.clear(); - mUsersAllowingNotifications.clear(); - // ... and refresh all the notifications - updateLockscreenNotificationSetting(); - notifyNotificationStateChanged(); + public void onChange(boolean selfChange, Collection<Uri> uris, int flags) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + @SuppressLint("MissingPermission") + List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + onChange(selfChange, uris, flags,users.get(i).getUserHandle()); + } + } else { + // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or + // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... + mUsersAllowingPrivateNotifications.clear(); + mUsersAllowingNotifications.clear(); + // ... and refresh all the notifications + updateLockscreenNotificationSetting(); + notifyNotificationStateChanged(); + } + } + + // Note: even though this is an override, this method is not called by the OS + // since we're not in system_server. We are using it internally for cases when + // we have a single user id available (e.g. from USER_ADDED). + @Override + public void onChange(boolean selfChange, Collection<Uri> uris, + int flags, UserHandle user) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = false; + for (Uri uri: uris) { + if (SHOW_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowSettings(user.getIdentifier()); + } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowPrivateSettings(user.getIdentifier()); + } + } + + if (mCurrentUserId == user.getIdentifier()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } } }; @@ -268,23 +376,26 @@ public class NotificationLockscreenUserManagerImpl implements }; mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + SHOW_LOCKSCREEN, false, mLockscreenSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + SHOW_PRIVATE_LOCKSCREEN, true, mLockscreenSettingsObserver, UserHandle.USER_ALL); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, - mSettingsObserver); + if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, + mSettingsObserver); + } mBroadcastDispatcher.registerReceiver(mAllUsersReceiver, new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - null /* handler */, UserHandle.ALL); + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundExecutor : null, UserHandle.ALL); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); @@ -305,7 +416,23 @@ public class NotificationLockscreenUserManagerImpl implements mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late updateCurrentProfilesCache(); - mSettingsObserver.onChange(false); // set up + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Set up + mBackgroundHandler.post(() -> { + @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + initValuesForUser(users.get(i).id); + } + }); + } else { + mSettingsObserver.onChange(false); // set up + } + } + + private void initValuesForUser(@UserIdInt int userId) { + mLockscreenSettingsObserver.onChange( + false, mLockScreenUris, 0, UserHandle.of(userId)); + updateDpcSettings(userId); } public boolean shouldShowLockscreenNotifications() { @@ -322,17 +449,75 @@ public class NotificationLockscreenUserManagerImpl implements mShowLockscreenNotifications = show; } - protected void updateLockscreenNotificationSetting() { - final boolean show = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - 1, - mCurrentUserId) != 0; - final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( - null /* admin */, mCurrentUserId); - final boolean allowedByDpm = (dpmFlags - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + protected boolean updateLockscreenNotificationSetting() { + boolean show; + boolean allowedByDpm; + + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + show = mUsersUsersAllowingNotifications.get(mCurrentUserId) + && mKeyguardAllowingNotifications; + // If DPC never notified us about a user, that means they have no policy for the user, + // and they allow the behavior + allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true); + } else { + show = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + mCurrentUserId) != 0; + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, mCurrentUserId); + allowedByDpm = (dpmFlags + & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + } + final boolean oldValue = mShowLockscreenNotifications; setShowLockscreenNotifications(show && allowedByDpm); + + return oldValue != mShowLockscreenNotifications; + } + + @WorkerThread + protected boolean updateDpcSettings(int userId) { + boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId); + boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId); + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, userId); + final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; + mUsersDpcAllowingNotifications.put(userId, allowedLockscreen); + mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate); + return (originalAllowLockscreen != allowedLockscreen) + || (originalAllowPrivate != allowedPrivate); + } + + @WorkerThread + private boolean updateUserShowSettings(int userId) { + boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId); + boolean newAllowLockscreen = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + userId) != 0; + mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen); + boolean keyguardChanged = updateGlobalKeyguardSettings(); + return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; + } + + @WorkerThread + private boolean updateUserShowPrivateSettings(int userId) { + boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId); + boolean newValue = mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + userId) != 0; + mUsersUsersAllowingPrivateNotifications.put(userId, newValue); + return (newValue != originalValue); + } + + @WorkerThread + private boolean updateGlobalKeyguardSettings() { + final boolean oldValue = mKeyguardAllowingNotifications; + mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed(); + return oldValue != mKeyguardAllowingNotifications; } /** @@ -340,21 +525,41 @@ public class NotificationLockscreenUserManagerImpl implements * when the lockscreen is in "public" (secure & locked) mode? */ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + if (userHandle == UserHandle.USER_ALL) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable()); + updateUserShowPrivateSettings(userHandle); + } + if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingPrivateNotifications.get(userHandle) + && mUsersDpcAllowingPrivateNotifications.get(userHandle); + } else { + if (userHandle == UserHandle.USER_ALL) { + return true; + } - if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - final boolean allowed = allowedByUser && allowedByDpm; - mUsersAllowingPrivateNotifications.append(userHandle, allowed); - return allowed; - } + if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); + return allowed; + } - return mUsersAllowingPrivateNotifications.get(userHandle); + return mUsersAllowingPrivateNotifications.get(userHandle); + } } /** @@ -406,21 +611,44 @@ public class NotificationLockscreenUserManagerImpl implements * "public" (secure & locked) mode? */ public boolean userAllowsNotificationsInPublic(int userHandle) { - if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Unlike 'show private', settings does not show a copy of this setting for each + // profile, so it inherits from the parent user. + if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable()); + updateUserShowSettings(userHandle); + } + if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingNotifications.get(userHandle) + && mUsersDpcAllowingNotifications.get(userHandle) + && mKeyguardAllowingNotifications; + } else { + if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { + return true; + } - if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); - final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; - mUsersAllowingNotifications.append(userHandle, allowed); - return allowed; + if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); + final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; + mUsersAllowingNotifications.append(userHandle, allowed); + return allowed; + } + return mUsersAllowingNotifications.get(userHandle); } - return mUsersAllowingNotifications.get(userHandle); } /** @return true if the entry needs redaction when on the lockscreen. */ @@ -451,9 +679,15 @@ public class NotificationLockscreenUserManagerImpl implements return true; } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); - return entry != null - && entry.getRanking().getLockscreenVisibilityOverride() - == Notification.VISIBILITY_PRIVATE; + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + return entry != null + && entry.getRanking().getChannel().getLockscreenVisibility() + == Notification.VISIBILITY_PRIVATE; + } else { + return entry != null + && entry.getRanking().getLockscreenVisibilityOverride() + == Notification.VISIBILITY_PRIVATE; + } } private void updateCurrentProfilesCache() { @@ -491,20 +725,6 @@ public class NotificationLockscreenUserManagerImpl implements } /** - * If any managed/work profiles are in public mode. - */ - public boolean isAnyManagedProfilePublicMode() { - synchronized (mLock) { - for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) { - if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) { - return true; - } - } - } - return false; - } - - /** * Returns the current user id. This can change if the user is switched. */ public int getCurrentUserId() { @@ -581,8 +801,16 @@ public class NotificationLockscreenUserManagerImpl implements } private void notifyNotificationStateChanged() { - for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { - listener.onNotificationStateChanged(); + if (!Looper.getMainLooper().isCurrentThread()) { + mMainHandler.post(() -> { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } + }); + } else { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } } } @@ -620,5 +848,15 @@ public class NotificationLockscreenUserManagerImpl implements pw.println(mUsersInLockdownLatestResult); pw.print(" mShouldHideNotifsLatestResult="); pw.println(mShouldHideNotifsLatestResult); + pw.print(" mUsersDpcAllowingNotifications="); + pw.println(mUsersDpcAllowingNotifications); + pw.print(" mUsersUsersAllowingNotifications="); + pw.println(mUsersUsersAllowingNotifications); + pw.print(" mKeyguardAllowingNotifications="); + pw.println(mKeyguardAllowingNotifications); + pw.print(" mUsersDpcAllowingPrivateNotifications="); + pw.println(mUsersDpcAllowingPrivateNotifications); + pw.print(" mUsersUsersAllowingPrivateNotifications="); + pw.println(mUsersUsersAllowingPrivateNotifications); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 5bd40b8ed714..389486f0ada3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,43 +15,29 @@ */ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK; -import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK; - -import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.os.AsyncTask; import android.os.Trace; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; -import android.util.ArraySet; import android.util.Log; import android.view.Display; import android.view.View; import android.widget.ImageView; -import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.models.player.MediaData; import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; @@ -65,18 +51,11 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.Utils; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -85,7 +64,6 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -99,7 +77,6 @@ public class NotificationMediaManager implements Dumpable { private final StatusBarStateController mStatusBarStateController; private final SysuiColorExtractor mColorExtractor; private final KeyguardStateController mKeyguardStateController; - private final KeyguardBypassController mKeyguardBypassController; private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>(); static { @@ -117,9 +94,6 @@ public class NotificationMediaManager implements Dumpable { private final NotifCollection mNotifCollection; @Nullable - private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - - @Nullable private BiometricUnlockController mBiometricUnlockController; @Nullable private ScrimController mScrimController; @@ -128,12 +102,8 @@ public class NotificationMediaManager implements Dumpable { @VisibleForTesting boolean mIsLockscreenLiveWallpaperEnabled; - private final DelayableExecutor mMainExecutor; - private final Context mContext; private final ArrayList<MediaListener> mMediaListeners; - private final MediaArtworkProcessor mMediaArtworkProcessor; - private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); protected NotificationPresenter mPresenter; private MediaController mMediaController; @@ -150,8 +120,6 @@ public class NotificationMediaManager implements Dumpable { private List<String> mSmallerInternalDisplayUids; private Display mCurrentDisplay; - private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -173,7 +141,6 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } - mMediaArtworkProcessor.clearCache(); mMediaMetadata = metadata; dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } @@ -184,13 +151,9 @@ public class NotificationMediaManager implements Dumpable { */ public NotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -199,12 +162,8 @@ public class NotificationMediaManager implements Dumpable { WallpaperManager wallpaperManager, DisplayManager displayManager) { mContext = context; - mMediaArtworkProcessor = mediaArtworkProcessor; - mKeyguardBypassController = keyguardBypassController; mMediaListeners = new ArrayList<>(); - mNotificationShadeWindowController = notificationShadeWindowController; mVisibilityProvider = visibilityProvider; - mMainExecutor = mainExecutor; mMediaDataManager = mediaDataManager; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; @@ -476,7 +435,6 @@ public class NotificationMediaManager implements Dumpable { } private void clearCurrentMediaNotificationSession() { - mMediaArtworkProcessor.clearCache(); mMediaMetadata = null; if (mMediaController != null) { if (DEBUG_MEDIA) { @@ -494,9 +452,6 @@ public class NotificationMediaManager implements Dumpable { public void onDisplayUpdated(Display display) { Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); mCurrentDisplay = display; - if (mWallapperDrawable != null) { - mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); - } Trace.endSection(); } @@ -531,18 +486,13 @@ public class NotificationMediaManager implements Dumpable { } /** - * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. + * Update media state of lockscreen media views and controllers . */ - public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { + public void updateMediaMetaData(boolean metaDataChanged) { if (mIsLockscreenLiveWallpaperEnabled) return; Trace.beginSection("CentralSurfaces#updateMediaMetaData"); - if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { - Trace.endSection(); - return; - } - if (getBackDropView() == null) { Trace.endSection(); return; // called too early @@ -566,168 +516,12 @@ public class NotificationMediaManager implements Dumpable { + " state=" + mStatusBarStateController.getState()); } - Bitmap artworkBitmap = null; - if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); - if (artworkBitmap == null) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - } - } - - // Process artwork on a background thread and send the resulting bitmap to - // finishUpdateMediaMetaData. - if (metaDataChanged) { - for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { - task.cancel(true); - } - mProcessArtworkTasks.clear(); - } - if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { - mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, - allowEnterAnimation).execute(artworkBitmap)); - } else { - finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); - } - - Trace.endSection(); - } - - private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, - @Nullable Bitmap bmp) { - Drawable artworkDrawable = null; - if (bmp != null) { - artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); - } - boolean hasMediaArtwork = artworkDrawable != null; - boolean allowWhenShade = false; - if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { - Bitmap lockWallpaper = - mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; - if (lockWallpaper != null) { - artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( - mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); - // We're in the SHADE mode on the SIM screen - yet we still need to show - // the lockscreen wallpaper in that mode. - allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; - } - } - - NotificationShadeWindowController windowController = - mNotificationShadeWindowController.get(); - boolean hideBecauseOccluded = mKeyguardStateController.isOccluded(); - - final boolean hasArtwork = artworkDrawable != null; - mColorExtractor.setHasMediaArtwork(hasMediaArtwork); + mColorExtractor.setHasMediaArtwork(false); if (mScrimController != null) { - mScrimController.setHasBackdrop(hasArtwork); + mScrimController.setHasBackdrop(false); } - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) - && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) - && mBiometricUnlockController != null && mBiometricUnlockController.getMode() - != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - && !hideBecauseOccluded) { - // time to show some art! - if (mBackdrop.getVisibility() != View.VISIBLE) { - mBackdrop.setVisibility(View.VISIBLE); - if (allowEnterAnimation) { - mBackdrop.setAlpha(0); - mBackdrop.animate().alpha(1f); - } else { - mBackdrop.animate().cancel(); - mBackdrop.setAlpha(1f); - } - if (windowController != null) { - windowController.setBackdropShowing(true); - } - metaDataChanged = true; - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); - } - } - if (metaDataChanged) { - if (mBackdropBack.getDrawable() != null) { - Drawable drawable = - mBackdropBack.getDrawable().getConstantState() - .newDrawable(mBackdropFront.getResources()).mutate(); - mBackdropFront.setImageDrawable(drawable); - mBackdropFront.setAlpha(1f); - mBackdropFront.setVisibility(View.VISIBLE); - } else { - mBackdropFront.setVisibility(View.INVISIBLE); - } - - if (DEBUG_MEDIA_FAKE_ARTWORK) { - final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); - Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); - mBackdropBack.setBackgroundColor(0xFFFFFFFF); - mBackdropBack.setImageDrawable(new ColorDrawable(c)); - } else { - if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { - mWallapperDrawable = - (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; - } - mBackdropBack.setImageDrawable(artworkDrawable); - } - - if (mBackdropFront.getVisibility() == View.VISIBLE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " - + mBackdropFront.getDrawable() - + " to " - + mBackdropBack.getDrawable()); - } - mBackdropFront.animate() - .setDuration(250) - .alpha(0f).withEndAction(mHideBackdropFront); - } - } - } else { - // need to hide the album art, either because we are unlocked, on AOD - // or because the metadata isn't there to support it - if (mBackdrop.getVisibility() != View.GONE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); - } - boolean cannotAnimateDoze = mStatusBarStateController.isDozing() - && !ScrimState.AOD.getAnimateChange(); - if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || cannotAnimateDoze)) - || hideBecauseOccluded) { - // We are unlocking directly - no animation! - mBackdrop.setVisibility(View.GONE); - mBackdropBack.setImageDrawable(null); - if (windowController != null) { - windowController.setBackdropShowing(false); - } - } else { - if (windowController != null) { - windowController.setBackdropShowing(false); - } - mBackdrop.animate() - .alpha(0) - .setInterpolator(Interpolators.ACCELERATE_DECELERATE) - .setDuration(300) - .setStartDelay(0) - .withEndAction(() -> { - mBackdrop.setVisibility(View.GONE); - mBackdropFront.animate().cancel(); - mBackdropBack.setImageDrawable(null); - mMainExecutor.execute(mHideBackdropFront); - }); - if (mKeyguardStateController.isKeyguardFadingAway()) { - mBackdrop.animate() - .setDuration( - mKeyguardStateController.getShortenedFadingAwayDuration()) - .setStartDelay( - mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setInterpolator(Interpolators.LINEAR) - .start(); - } - } - } - } + Trace.endSection(); } public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, @@ -758,15 +552,6 @@ public class NotificationMediaManager implements Dumpable { } }; - private Bitmap processArtwork(Bitmap artwork) { - return mMediaArtworkProcessor.processArtwork(mContext, artwork); - } - - @MainThread - private void removeTask(AsyncTask<?, ?, ?> task) { - mProcessArtworkTasks.remove(task); - } - // TODO(b/273443374): remove public boolean isLockscreenWallpaperOnNotificationShade() { return mBackdrop != null && mLockscreenWallpaper != null @@ -780,52 +565,6 @@ public class NotificationMediaManager implements Dumpable { return mBackdrop; } - /** - * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. - */ - private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { - - private final WeakReference<NotificationMediaManager> mManagerRef; - private final boolean mMetaDataChanged; - private final boolean mAllowEnterAnimation; - - ProcessArtworkTask(NotificationMediaManager manager, boolean changed, - boolean allowAnimation) { - mManagerRef = new WeakReference<>(manager); - mMetaDataChanged = changed; - mAllowEnterAnimation = allowAnimation; - } - - @Override - protected Bitmap doInBackground(Bitmap... bitmaps) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager == null || bitmaps.length == 0 || isCancelled()) { - return null; - } - return manager.processArtwork(bitmaps[0]); - } - - @Override - protected void onPostExecute(@Nullable Bitmap result) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null && !isCancelled()) { - manager.removeTask(this); - manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); - } - } - - @Override - protected void onCancelled(Bitmap result) { - if (result != null) { - result.recycle(); - } - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null) { - manager.removeTask(this); - } - } - } - public interface MediaListener { /** * Called whenever there's new metadata or playback state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index d4b6dfb9b625..61ebcc0c99d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar; + import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.KeyguardManager; @@ -48,10 +49,10 @@ import androidx.annotation.Nullable; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.res.R; import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; @@ -535,7 +536,8 @@ public class NotificationRemoteInputManager implements Dumpable { public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) { if (isRemoteInputActive(entry)) { entry.mRemoteEditImeVisible = false; - mRemoteInputController.removeRemoteInput(entry, null); + mRemoteInputController.removeRemoteInput(entry, null, + /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f8c049e86cb8..23b697e5554b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -133,7 +133,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayoutController hostLayoutController) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -143,7 +143,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout, NotificationRoundnessManager roundnessManager) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; mAmbientState = ambientState; mHostLayout = hostLayout; mRoundnessManager = roundnessManager; @@ -964,7 +964,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St @Override public void onStateChanged(int newState) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mStatusBarState = newState; updateInteractiveness(); } @@ -1022,17 +1022,17 @@ public class NotificationShelf extends ActivatableNotificationView implements St } public void setController(NotificationShelfController notificationShelfController) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mController = notificationShelfController; } public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; mCanModifyColorOfNotifications = canModifyColorOfNotifications; } public void setCanInteract(boolean canInteract) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; mCanInteract = canInteract; updateInteractiveness(); } @@ -1050,7 +1050,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } public void requestRoundnessResetFor(ExpandableView child) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; child.requestRoundnessReset(SHELF_SCROLL); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index d5e4902366e7..17da015789ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -31,6 +31,8 @@ import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.util.DumpUtilsKt; +import com.google.errorprone.annotations.CompileTimeConstant; + import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Objects; @@ -69,7 +71,8 @@ public class RemoteInputController { * @param entry the entry for which a remote input is now active. * @param token a token identifying the view that is managing the remote input */ - public void addRemoteInput(NotificationEntry entry, Object token) { + public void addRemoteInput(NotificationEntry entry, Object token, + @CompileTimeConstant String reason) { Objects.requireNonNull(entry); Objects.requireNonNull(token); boolean isActive = isRemoteInputActive(entry); @@ -77,7 +80,9 @@ public class RemoteInputController { entry /* contains */, null /* remove */, token /* removeToken */); mLogger.logAddRemoteInput(entry.getKey()/* entryKey */, isActive /* isRemoteInputAlreadyActive */, - found /* isRemoteInputFound */); + found /* isRemoteInputFound */, + reason /* reason */, + entry.getNotificationStyle()/* notificationStyle */); if (!found) { mOpen.add(new Pair<>(new WeakReference<>(entry), token)); } @@ -96,7 +101,8 @@ public class RemoteInputController { * the entry is only removed if the token matches the last added token for this * entry. If null, the entry is removed regardless. */ - public void removeRemoteInput(NotificationEntry entry, Object token) { + public void removeRemoteInput(NotificationEntry entry, Object token, + @CompileTimeConstant String reason) { Objects.requireNonNull(entry); if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) { mLogger.logRemoveRemoteInput( @@ -104,9 +110,12 @@ public class RemoteInputController { true /* remoteEditImeVisible */, true /* remoteEditImeAnimatingAway */, isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */, - isRemoteInputActive() /* isRemoteInputActive */); + isRemoteInputActive() /* isRemoteInputActive */, + reason /* reason */, + entry.getNotificationStyle()/* notificationStyle */); return; } + // If the view is being removed, this may be called even though we're not active boolean remoteInputActiveForEntry = isRemoteInputActive(entry); mLogger.logRemoveRemoteInput( @@ -114,7 +123,9 @@ public class RemoteInputController { entry.mRemoteEditImeVisible /* remoteEditImeVisible */, entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */, remoteInputActiveForEntry /* isRemoteInputActiveForEntry */, - isRemoteInputActive()/* isRemoteInputActive */); + isRemoteInputActive()/* isRemoteInputActive */, + reason/* reason */, + entry.getNotificationStyle()/* notificationStyle */); if (!remoteInputActiveForEntry) return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java index 1196211bd671..595ab701a9f2 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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 7f5829d81c6f..125c8efe1884 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -31,7 +31,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -45,12 +44,10 @@ import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -61,7 +58,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -71,7 +67,6 @@ import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputUriController; -import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.Binds; import dagger.Lazy; @@ -122,13 +117,9 @@ public interface CentralSurfacesDependenciesModule { @Provides static NotificationMediaManager provideNotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -138,13 +129,9 @@ public interface CentralSurfacesDependenciesModule { DisplayManager displayManager) { return new NotificationMediaManager( context, - notificationShadeWindowController, visibilityProvider, - mediaArtworkProcessor, - keyguardBypassController, notifPipeline, notifCollection, - mainExecutor, mediaDataManager, statusBarStateController, colorExtractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java deleted file mode 100644 index 732c115571f8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2017 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.notification; - -import android.graphics.Bitmap; -import android.graphics.Color; - -import androidx.palette.graphics.Palette; - -import java.util.List; - -/** - * A gutted class that now contains only a color extraction utility used by the - * MediaArtworkProcessor, which has otherwise supplanted this. - * - * TODO(b/182926117): move this into MediaArtworkProcessor.kt - */ -public class MediaNotificationProcessor { - - /** - * The population fraction to select a white or black color as the background over a color. - */ - private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; - private static final float BLACK_MAX_LIGHTNESS = 0.08f; - private static final float WHITE_MIN_LIGHTNESS = 0.90f; - private static final int RESIZE_BITMAP_AREA = 150 * 150; - - private MediaNotificationProcessor() { - } - - /** - * Finds an appropriate background swatch from media artwork. - * - * @param artwork Media artwork - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) { - return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate()); - } - - /** - * Finds an appropriate background swatch from the palette of media artwork. - * - * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Palette palette) { - // by default we use the dominant palette - Palette.Swatch dominantSwatch = palette.getDominantSwatch(); - if (dominantSwatch == null) { - return new Palette.Swatch(Color.WHITE, 100); - } - - if (!isWhiteOrBlack(dominantSwatch.getHsl())) { - return dominantSwatch; - } - // Oh well, we selected black or white. Lets look at the second color! - List<Palette.Swatch> swatches = palette.getSwatches(); - float highestNonWhitePopulation = -1; - Palette.Swatch second = null; - for (Palette.Swatch swatch : swatches) { - if (swatch != dominantSwatch - && swatch.getPopulation() > highestNonWhitePopulation - && !isWhiteOrBlack(swatch.getHsl())) { - second = swatch; - highestNonWhitePopulation = swatch.getPopulation(); - } - } - if (second == null) { - return dominantSwatch; - } - if (dominantSwatch.getPopulation() / highestNonWhitePopulation - > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { - // The dominant swatch is very dominant, lets take it! - // We're not filtering on white or black - return dominantSwatch; - } else { - return second; - } - } - - /** - * Generate a palette builder for media artwork. - * - * For producing a smooth background transition, the palette is extracted from only the left - * side of the artwork. - * - * @param artwork Media artwork - * @return Builder that generates the {@link Palette} for the media artwork. - */ - public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { - // for the background we only take the left side of the image to ensure - // a smooth transition - return Palette.from(artwork) - .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight()) - .clearFilters() // we want all colors, red / white / black ones too! - .resizeBitmapArea(RESIZE_BITMAP_AREA); - } - - private static boolean isWhiteOrBlack(float[] hsl) { - return isBlack(hsl) || isWhite(hsl); - } - - /** - * @return true if the color represents a color which is close to black. - */ - private static boolean isBlack(float[] hslColor) { - return hslColor[2] <= BLACK_MAX_LIGHTNESS; - } - - /** - * @return true if the color represents a color which is close to white. - */ - private static boolean isWhite(float[] hslColor) { - return hslColor[2] >= WHITE_MIN_LIGHTNESS; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index 39b999cb4f35..ff89c62ab496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -32,17 +32,24 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) { fun logAddRemoteInput( entryKey: String, isRemoteInputAlreadyActive: Boolean, - isRemoteInputFound: Boolean + isRemoteInputFound: Boolean, + reason: String, + notificationStyle: String ) = logBuffer.log( TAG, DEBUG, { str1 = entryKey + str2 = reason + str3 = notificationStyle bool1 = isRemoteInputAlreadyActive bool2 = isRemoteInputFound }, - { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" } + { + "addRemoteInput reason:$str2 entry: $str1, style:$str3" + + ", isAlreadyActive: $bool1, isFound:$bool2" + } ) /** logs removeRemoteInput invocation of [RemoteInputController] */ @@ -52,20 +59,25 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) { remoteEditImeVisible: Boolean, remoteEditImeAnimatingAway: Boolean, isRemoteInputActiveForEntry: Boolean, - isRemoteInputActive: Boolean + isRemoteInputActive: Boolean, + reason: String, + notificationStyle: String ) = logBuffer.log( TAG, DEBUG, { str1 = entryKey + str2 = reason + str3 = notificationStyle bool1 = remoteEditImeVisible bool2 = remoteEditImeAnimatingAway bool3 = isRemoteInputActiveForEntry bool4 = isRemoteInputActive }, { - "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" + + "removeRemoteInput reason: $str2 entry: $str1" + + ", style: $str3, remoteEditImeVisible: $bool1" + ", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" + ", isRemoteInputActive: $bool4" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index affd2d186774..4573d5989faa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -979,6 +979,19 @@ public final class NotificationEntry extends ListEntry { return mExpandAnimationRunning; } + /** + * @return NotificationStyle + */ + public String getNotificationStyle() { + if (isSummaryWithChildren()) { + return "summary"; + } + + final Class<? extends Notification.Style> style = + getSbn().getNotification().getNotificationStyle(); + return style == null ? "nostyle" : style.getSimpleName(); + } + /** Information about a suggestion that is being edited. */ public static class EditedSuggestionInfo { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9d56a8ede1cc..362786ec4b58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -115,6 +115,7 @@ public interface NotifCollectionListener { * * @deprecated Use {@link #onRankingApplied()} instead. */ + @Deprecated default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index 50efbb5458cf..eb5c1fa3b0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -167,17 +167,16 @@ constructor( NotificationShelfViewBinderWrapperControllerImpl.unsupported override fun setShelfIcons(icons: NotificationIconContainer) { - if (shelfRefactor.expectEnabled()) { - NotificationIconContainerViewBinder.bind( - icons, - shelfIconsViewModel, - configurationController, - dozeParameters, - featureFlags, - screenOffAnimationController, - ) - shelfIcons = icons - } + if (shelfRefactor.isUnexpectedlyInLegacyMode()) return + NotificationIconContainerViewBinder.bind( + icons, + shelfIconsViewModel, + configurationController, + dozeParameters, + featureFlags, + screenOffAnimationController, + ) + shelfIcons = icons } override fun onDensityOrFontScaleChanged(context: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index b2c32cd42d1d..db7f46eb28f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -1,6 +1,5 @@ package com.android.systemui.statusbar.notification.interruption -import android.app.Notification import android.app.Notification.VISIBILITY_SECRET import android.content.Context import android.database.ContentObserver @@ -14,6 +13,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -78,7 +79,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val userTracker: UserTracker, private val secureSettings: SecureSettings, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val featureFlags: FeatureFlagsClassic ) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) @@ -201,7 +203,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // device isn't public, no need to check public-related settings, so allow !lockscreenUserManager.isLockscreenPublicMode(user) -> false // entry is meant to be secret on the lockscreen, disallow - entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true + isRankingVisibilitySecret(entry) -> true // disallow if user disallows notifications in public else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) } @@ -215,6 +217,17 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( } } + private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean { + return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting + // info, and NotificationLockscreenUserManagerImpl is already listening for updates + // to those + entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET + } else { + entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET + } + } + override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { println("isLockedOrLocking=$isLockedOrLocking") withIncreasedIndent { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 847d94861401..661768da8479 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -351,12 +351,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; - startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onFinishedRunnable, animationListener); + if (mDrawingAppearAnimation) { + startAppearAnimation(false /* isAppearing */, translationDirection, + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + } else { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + } return 0; } @@ -365,12 +374,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView Runnable onFinishRunnable) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAppear; - startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null); + if (mDrawingAppearAnimation) { + startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, + duration, null, null, null); + } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, - long duration, final Runnable onFinishedRunnable, + long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); @@ -434,6 +445,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index bc570f2cae35..11c65e542bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1901,16 +1901,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return traceTag; } - if (isSummaryWithChildren()) { - return traceTag + "(summary)"; - } - Class<? extends Notification.Style> style = - getEntry().getSbn().getNotification().getNotificationStyle(); - if (style == null) { - return traceTag + "(nostyle)"; - } else { - return traceTag + "(" + style.getSimpleName() + ")"; - } + return traceTag + "(" + getEntry().getNotificationStyle() + ")"; } @Override @@ -2927,6 +2918,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2934,10 +2926,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (anim != null) { anim.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + } + @Override public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener); } }); anim.start(); @@ -2945,7 +2943,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f2f55a87ba3f..6edab4d26d59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,6 +69,9 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private boolean mClipToActualHeight = true; private boolean mChangingPosition = false; private ViewGroup mTransientContainer; + + // Needs to be added as transient view when removed from parent, because it's in animation + private boolean mInRemovalAnimation; private boolean mInShelf; private boolean mTransformingInShelf; protected float mContentTransformationAmount; @@ -381,6 +384,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro */ public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); @@ -604,6 +608,25 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro } /** + * Add the view to a transient container. + */ + public void addToTransientContainer(ViewGroup container, int index) { + container.addTransientView(this, index); + setTransientContainer(container); + } + + /** + * @return If the view is in a process of removal animation. + */ + public boolean inRemovalAnimation() { + return mInRemovalAnimation; + } + + public void setInRemovalAnimation(boolean inRemovalAnimation) { + mInRemovalAnimation = inRemovalAnimation; + } + + /** * @return true if the group's expansion state is changing, false otherwise. */ public boolean isGroupExpansionChanging() { @@ -837,6 +860,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro pw.println(); } if (DUMP_VERBOSE) { + pw.println("mInRemovalAnimation: " + mInRemovalAnimation); pw.println("mClipTopAmount: " + mClipTopAmount); pw.println("mClipBottomAmount " + mClipBottomAmount); pw.println("mClipToActualHeight: " + mClipToActualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index a27a305428c4..60e75ff9e6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1363,12 +1363,12 @@ public class NotificationContentView extends FrameLayout implements Notification result.mController.setPendingIntent(existingPendingIntent); } if (result.mController.updatePendingIntentFromActions(actions)) { - if (!result.mView.isActive()) { - result.mView.focus(); + if (!result.mController.isActive()) { + result.mController.focus(); } } else { - if (result.mView.isActive()) { - result.mView.close(); + if (result.mController.isActive()) { + result.mController.close(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index e200b901e815..aabf2954f23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -237,9 +237,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5d46f52dba87..bae5baaf91ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -25,9 +25,7 @@ import android.util.AttributeSet import com.android.systemui.res.R import com.android.systemui.statusbar.notification.row.ExpandableView -/** - * Root view to insert Lock screen media controls into the notification stack. - */ +/** Root view to insert Lock screen media controls into the notification stack. */ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) { override var clipHeight = 0 @@ -46,8 +44,8 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } private fun updateResources() { - cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() + cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() } public override fun updateClipping() { @@ -70,18 +68,23 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? ): Long { return 0 } - override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean, - onEnd: Runnable?) { + override fun performAddAnimation( + delay: Long, + duration: Long, + isHeadsUpAppear: Boolean, + onEnd: Runnable? + ) { // No animation, it doesn't need it, this would be local } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 6f3cd5d52e1e..dba93d9718cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; +import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -576,6 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSplitShadeStateController = splitShadeStateController; updateSplitNotificationShade(); } + private FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -628,16 +630,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public NotificationStackScrollLayout(Context context, AttributeSet attrs) { super(context, attrs, 0, 0); Resources res = getResources(); - FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); - mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled( + mFeatureFlags = Dependency.get(FeatureFlags.class); + mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled( Flags.LOCKSCREEN_ENABLE_LANDSCAPE); - mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); - mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); - mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); - mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); + mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); + mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); + mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); + mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); mAnimatedInsets = - new RefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); - mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -2733,7 +2735,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param listener callback for notification removed */ public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mOnNotificationRemovedListener = listener; } @@ -2779,8 +2781,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (animationGenerated) { if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { logAddTransientChild(child, container); - container.addTransientView(child, 0); - child.setTransientContainer(container); + child.addToTransientContainer(container, 0); } } else { mSwipedOutViews.remove(child); @@ -2870,7 +2871,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Generate a remove animation for a child view. * * @param child The view to generate the remove animation for. - * @return Whether an animation was generated. + * @return Whether a new animation was generated or an existing animation was detected by this + * method. We need this to determine if a transient view is needed. */ boolean generateRemoveAnimation(ExpandableView child) { String key = ""; @@ -2887,10 +2889,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.remove(child); return false; } - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; + if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); + } + } else { + if (isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return true; + } } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key @@ -4967,12 +4982,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Nullable public ExpandableView getShelf() { - if (!mShelfRefactor.expectEnabled()) return null; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null; return mShelf; } public void setShelf(NotificationShelf shelf) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); @@ -4986,7 +5001,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public void setShelfController(NotificationShelfController notificationShelfController) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 50207806ecaa..6a70815f82f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1431,7 +1431,7 @@ public class NotificationStackScrollLayoutController { } public void setShelfController(NotificationShelfController notificationShelfController) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mView.setShelfController(notificationShelfController); } @@ -1644,12 +1644,12 @@ public class NotificationStackScrollLayoutController { } public void setShelf(NotificationShelf shelf) { - if (!mShelfRefactor.expectEnabled()) return; + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; mView.setShelf(shelf); } public int getShelfHeight() { - if (!mShelfRefactor.expectEnabled()) { + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) { return 0; } ExpandableView shelf = mView.getShelf(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 69453c65f57d..e94258f416ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -346,21 +349,19 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { boolean needsCustomAnimation = false; for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { - final ExpandableView changingView = (ExpandableView) event.mChangingView; + final ExpandableView changingView = event.mChangingView; boolean loggable = false; boolean isHeadsUp = false; - boolean isGroupChild = false; String key = null; if (changingView instanceof ExpandableNotificationRow && mLogger != null) { loggable = true; isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp(); - isGroupChild = changingView.isChildInGroup(); key = ((ExpandableNotificationRow) changingView).getEntry().getKey(); } if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); if (viewState == null || viewState.gone) { // The position for this child was never generated, let's continue. @@ -374,7 +375,11 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { - if (changingView.getVisibility() != View.VISIBLE) { + int changingViewVisibility = changingView.getVisibility(); + if (loggable) { + mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp); + } + if (changingViewVisibility != View.VISIBLE) { changingView.removeFromTransientContainer(); continue; } @@ -410,30 +415,40 @@ public class StackStateAnimator { translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); } - Runnable postAnimation = changingView::removeFromTransientContainer; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { String finalKey = key; - if (isHeadsUp) { - mLogger.logHUNViewDisappearingWithRemoveEvent(key); - postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } else if (isGroupChild) { - mLogger.groupChildRemovalEventProcessed(key); - postAnimation = () -> { - mLogger.groupChildRemovalAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } + final boolean finalIsHeadsHp = isHeadsUp; + startAnimation = () -> { + mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; + } else { + startAnimation = ()-> { + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener()); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (mHostLayout.isFullySwipedOut(changingView)) { + boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView); + if (loggable) { + mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp); + } + if (isFullySwipedOut) { changingView.removeFromTransientContainer(); } } else if (event.animationType == NotificationStackScrollLayout @@ -442,7 +457,7 @@ public class StackStateAnimator { row.prepareExpansionChanged(); } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); mTmpState.copyFrom(viewState); if (event.headsUpFromBottom) { @@ -464,22 +479,23 @@ public class StackStateAnimator { } mTmpState.applyToView(changingView); - } else if (event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || - event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); Runnable endRunnable = null; if (changingView.getParent() == null) { - // This notification was actually removed, so we need to add it transiently + // This notification was actually removed, so we need to add it + // transiently mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } + boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } @@ -488,21 +504,43 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - Runnable postAnimation = endRunnable; + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { - mLogger.logHUNViewDisappearing(key); - - Runnable finalEndRunnable = endRunnable; String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = + event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR" + : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + } else { postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey1); - if (finalEndRunnable != null) finalEndRunnable.run(); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, + getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 0b2c4863157c..d635f8938491 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -5,74 +5,104 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.util.visibilityString import javax.inject.Inject -class StackStateLogger @Inject constructor( +class StackStateLogger +@Inject +constructor( @NotificationHeadsUpLog private val buffer: LogBuffer, @NotificationRenderLog private val notificationRenderBuffer: LogBuffer ) { - fun logHUNViewDisappearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 " - }) - } fun logHUNViewAppearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification view appearing $str1 " - }) + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification view appearing $str1 " } + ) } - fun logHUNViewDisappearingWithRemoveEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" - }) + fun logHUNViewAppearingWithAddEvent(key: String) { + buffer.log( + TAG, + LogLevel.ERROR, + { str1 = logKey(key) }, + { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" } + ) } - fun logHUNViewAppearingWithAddEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" - }) + fun appearAnimationEnded(key: String) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification appear animation ended $str1 " } + ) } - fun disappearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification disappear animation ended $str1 " - }) + fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + int1 = visibility + bool1 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " + + "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1" + } + ) } - fun appearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification appear animation ended $str1 " - }) + fun processAnimationEventsRemoveSwipeOut( + key: String, + isFullySwipedOut: Boolean, + isHeadsUp: Boolean + ) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + bool1 = isFullySwipedOut + bool2 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " + + "isFullySwipedOut: $bool1, isHeadsUp: $bool2" + } + ) } - fun groupChildRemovalEventProcessed(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.DEBUG, { - str1 = logKey(key) - }, { - "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE" - }) + fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } - fun groupChildRemovalAnimationEnded(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Group child notification removal animation ended $str1 " - }) + + fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } } -private const val TAG = "StackScroll"
\ No newline at end of file +private const val TAG = "StackScroll" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f750feda9091..1229cb9b49ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** View-model for the shared notification container, used by both the shade and keyguard spaces */ class SharedNotificationContainerViewModel @@ -45,8 +46,8 @@ constructor( ) { private val statesForConstrainedNotifications = setOf( - KeyguardState.LOCKSCREEN, KeyguardState.AOD, + KeyguardState.LOCKSCREEN, KeyguardState.DOZING, KeyguardState.ALTERNATE_BOUNCER, KeyguardState.PRIMARY_BOUNCER @@ -68,8 +69,17 @@ constructor( /** If the user is visually on one of the unoccluded lockscreen states. */ val isOnLockscreen: Flow<Boolean> = - keyguardTransitionInteractor.finishedKeyguardState - .map { statesForConstrainedNotifications.contains(it) } + combine( + keyguardTransitionInteractor.finishedKeyguardState.map { + statesForConstrainedNotifications.contains(it) + }, + keyguardTransitionInteractor + .transitionValue(KeyguardState.LOCKSCREEN) + .onStart { emit(0f) } + .map { it > 0 } + ) { constrainedNotificationState, transitioningToOrFromLockscreen -> + constrainedNotificationState || transitioningToOrFromLockscreen + } .distinctUntilChanged() /** Are we purely on the keyguard without the shade/qs? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2809cad34143..8129b83a22d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; -import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; import android.annotation.IntDef; import android.content.res.Resources; @@ -30,7 +28,6 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; import android.os.Trace; -import android.view.HapticFeedbackConstants; import androidx.annotation.Nullable; @@ -47,16 +44,17 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; @@ -73,12 +71,14 @@ import java.util.Set; import javax.inject.Inject; +import kotlinx.coroutines.ExperimentalCoroutinesApi; + /** * Controller which coordinates all the biometric unlocking actions with the UI. */ +@ExperimentalCoroutinesApi @SysUISingleton public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { - private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L; private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -175,6 +175,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final BiometricUnlockLogger mLogger; private final SystemClock mSystemClock; private final boolean mOrderUnlockAndWake; + private final DeviceEntryHapticsInteractor mHapticsInteractor; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -284,7 +285,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, SystemClock systemClock, - FeatureFlags featureFlags + FeatureFlags featureFlags, + DeviceEntryHapticsInteractor hapticsInteractor ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -314,6 +316,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mFeatureFlags = featureFlags; mOrderUnlockAndWake = resources.getBoolean( com.android.internal.R.bool.config_orderUnlockAndWake); + mHapticsInteractor = hapticsInteractor; dumpManager.registerDumpable(this); } @@ -434,7 +437,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { - vibrateSuccess(biometricSourceType); + mHapticsInteractor.vibrateSuccess(); onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); @@ -498,8 +501,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING"); - mMediaManager.updateMediaMetaData(false /* metaDataChanged */, - true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(false /* metaDataChanged */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); } else { @@ -723,7 +725,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser())) || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { - vibrateError(biometricSourceType); + mHapticsInteractor.vibrateError(); } cleanup(); @@ -750,45 +752,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - // these haptics are for device-entry only - private void vibrateSuccess(BiometricSourceType type) { - if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) - && lastWakeupFromPowerButtonWithinHapticThreshold()) { - mLogger.d("Skip auth success haptic. Power button was recently pressed."); - return; - } - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.CONFIRM - ); - } else { - mVibratorHelper.vibrateAuthSuccess( - getClass().getSimpleName() + ", type =" + type + "device-entry::success"); - } - } - - private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { - final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON; - return lastWakeupFromPowerButton - && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME - && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() - < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; - } - - private void vibrateError(BiometricSourceType type) { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.REJECT - ); - } else { - mVibratorHelper.vibrateAuthError( - getClass().getSimpleName() + ", type =" + type + "device-entry::error"); - } - } - private void cleanup() { releaseBiometricWakeLock(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index ed1c4ece1dbf..9fb6c1bb2fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -312,8 +312,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - updateBubblesVisibility(); mStatusBarWindowState = state; + updateBubblesVisibility(); } @Override @@ -1088,6 +1088,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * @deprecated use {@link * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. */ @VisibleForTesting + @Deprecated void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1725,7 +1726,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( mode != StatusBarMode.LIGHTS_OUT - && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT)); + && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT + && mStatusBarWindowState != WINDOW_STATE_HIDDEN)); } void checkBarMode( 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 1576aa2a4a0c..6f992ac815cb 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 de9854afdba1..5deb08a75dff 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/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index fb5a530e3875..0a03af7d9387 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 58126ae41a1d..8a64a509a0e9 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/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 5553270ed0ae..f9856b0415e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -205,14 +205,13 @@ public class LegacyNotificationIconAreaControllerImpl implements } public void setupShelf(NotificationShelfController notificationShelfController) { - mShelfRefactor.assertDisabled(); + mShelfRefactor.assertInLegacyMode(); mShelfIcons = notificationShelfController.getShelfIcons(); } public void setShelfIcons(NotificationIconContainer icons) { - if (mShelfRefactor.expectEnabled()) { - mShelfIcons = icons; - } + if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; + mShelfIcons = icons; } public void onDensityOrFontScaleChanged(@NotNull Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 92c786fb569f..00fd9fbfffe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -263,8 +263,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen if (result.success) { mCached = true; mCache = result.bitmap; - mMediaManager.updateMediaMetaData( - true /* metaDataChanged */, true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(true /* metaDataChanged */); } mLoader = null; } 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 ffeb1a8cb8b6..9ae41951bb74 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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3adf3385e3cc..400ac7b415b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -961,7 +961,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } if (isShowing) { - mMediaManager.updateMediaMetaData(false, animate && !isOccluded); + mMediaManager.updateMediaMetaData(false); } mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 2d14f6b3c508..57a8e6fe0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -221,7 +221,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { - mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); + mMediaManager.updateMediaMetaData(metaDataChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index 6dc8065b2822..da91d6a05d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -15,6 +15,9 @@ */ package com.android.systemui.statusbar.phone; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + +@WeaklyReferencedCallback public interface StatusBarWindowCallback { /** * Invoked when the internal state of NotificationShadeWindowControllerImpl changes. 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 000000000000..85fd2afed9ec --- /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/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 2c15e27b8148..de37170b1f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.TraceUtils import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags /** * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely @@ -65,7 +67,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>, private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, - private val handler: Handler = Handler() + private val handler: Handler = Handler(), + private val featureFlags: FeatureFlags, ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -285,7 +288,11 @@ class UnlockedScreenOffAnimationController @Inject constructor( // up, with unpredictable consequences. if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && shouldAnimateInKeyguard) { - aodUiAnimationPlaying = true + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + // Tracking this state should no longer be relevant, as the isInteractive + // check covers it + aodUiAnimationPlaying = true + } // Show AOD. That'll cause the KeyguardVisibilityHelper to call // #animateInKeyguard. 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 aacdc6323179..3522b9a13989 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 a89b1b2db6b3..679426db99c0 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 c576b822da15..caa9d1a8f0c3 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 d4ddb856eecf..4cd877eb1a14 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 8b03f71a2729..0aa95f8821cc 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 28be3be28928..27edd1ec0caa 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 ee11c06ef3f5..6b6192186e0f 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 dc50990d002a..760dd7eba3cf 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 4bf297cd088c..fe49c0791370 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 f0470ca0a1ad..b93e44378280 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 dfabeea96082..d88c9efdac76 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 0f55910d8779..be843ba8699f 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 81f8683411ca..790596ea5520 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 fe69d818dedb..3b87bed2e0ef 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/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index e9e52a2397e1..1670dd39ba24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -104,7 +104,7 @@ constructor( val callback = object : WifiPickerTracker.WifiPickerTrackerCallback { override fun onWifiEntriesChanged() { - val connectedEntry = wifiPickerTracker?.connectedWifiEntry + val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection logOnWifiEntriesChanged(connectedEntry) val secondaryNetworks = @@ -217,6 +217,21 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, emptyList()) /** + * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the + * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry + * if it exists, falling back on the connected entry if null + */ + private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry? + get() { + val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry + return if (mergedEntry != null && mergedEntry.isDefaultNetwork) { + mergedEntry + } else { + this?.connectedWifiEntry + } + } + + /** * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the * primary network. Returns an inactive network if it's not primary. */ 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 a9ac51d7c472..60055279055c 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/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 945cc6bc2519..53b343c09329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; @@ -36,6 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.bluetooth.BluetoothLogger; 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.settings.UserTracker; @@ -81,6 +83,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private int mState; private final BluetoothAdapter mAdapter; + + private final Executor mBackgroundExecutor; /** */ @Inject @@ -90,6 +94,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa DumpManager dumpManager, BluetoothLogger logger, BluetoothRepository bluetoothRepository, + @Background Executor executor, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { @@ -98,6 +103,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); + mBackgroundExecutor = executor; if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); @@ -218,6 +224,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mIsActive; } + @WorkerThread @Override public void setBluetoothEnabled(boolean enabled) { if (mLocalBluetoothManager != null) { @@ -230,6 +237,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mLocalBluetoothManager != null; } + @WorkerThread @Override public String getConnectedDeviceName() { synchronized (mConnectedDevices) { @@ -251,6 +259,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa getDevices(), this::onConnectionStatusFetched); } + // Careful! This may be invoked in the main thread. private void onConnectionStatusFetched(ConnectionStatusModel status) { List<CachedBluetoothDevice> newList = status.getConnectedDevices(); int state = status.getMaxConnectionState(); @@ -282,30 +291,33 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateAudioProfile() { - boolean audioProfileConnected = false; - boolean otherProfileConnected = false; - - for (CachedBluetoothDevice device : getDevices()) { - for (LocalBluetoothProfile profile : device.getProfiles()) { - int profileId = profile.getProfileId(); - boolean isConnected = device.isConnectedProfile(profile); - if (profileId == BluetoothProfile.HEADSET - || profileId == BluetoothProfile.A2DP - || profileId == BluetoothProfile.HEARING_AID - || profileId == BluetoothProfile.LE_AUDIO) { - audioProfileConnected |= isConnected; - } else { - otherProfileConnected |= isConnected; + // We want this in the background as calls inside `LocalBluetoothProfile` end up being + // binder calls + mBackgroundExecutor.execute(() -> { + boolean audioProfileConnected = false; + boolean otherProfileConnected = false; + + for (CachedBluetoothDevice device : getDevices()) { + for (LocalBluetoothProfile profile : device.getProfiles()) { + int profileId = profile.getProfileId(); + boolean isConnected = device.isConnectedProfile(profile); + if (profileId == BluetoothProfile.HEADSET + || profileId == BluetoothProfile.A2DP + || profileId == BluetoothProfile.HEARING_AID + || profileId == BluetoothProfile.LE_AUDIO) { + audioProfileConnected |= isConnected; + } else { + otherProfileConnected |= isConnected; + } } } - } - - boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); - if (audioProfileOnly != mAudioProfileOnly) { - mAudioProfileOnly = audioProfileOnly; - mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); - } + boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); + if (audioProfileOnly != mAudioProfileOnly) { + mAudioProfileOnly = audioProfileOnly; + mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 62e238164514..b614b6d0547d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -38,6 +38,7 @@ import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.res.R; import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.user.UserSwitchDialogController; @@ -148,6 +149,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, + FeatureFlags featureFlags, UiEventLogger uiEventLogger) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); @@ -160,7 +162,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mStatusBarStateController = statusBarStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, - screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); + screenOffAnimationController, /* animateYPos= */ false, + featureFlags, /* logBuffer= */ null); mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index bb074ac33ddb..dfe26865f978 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -38,10 +38,11 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -160,6 +161,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, + FeatureFlags featureFlags, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController) { super(keyguardUserSwitcherView); @@ -174,7 +176,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mUserSwitcherController, this); mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, - screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); + screenOffAnimationController, /* animateYPos= */ false, + featureFlags, /* logBuffer= */ null); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 53fed3d2438f..ceed81a182aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -305,7 +305,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime()); if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) { // Pass null to ensure all inputs are cleared for this entry b/227115380 - mController.removeRemoteInput(mEntry, null); + mController.removeRemoteInput(mEntry, null, + /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd"); } } } @@ -426,7 +427,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @VisibleForTesting void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) { - mController.removeRemoteInput(mEntry, mToken); + mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus"); mEntry.remoteInputText = mEditText.getText(); // During removal, we get reattached and lose focus. Not hiding in that @@ -536,7 +537,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) { return; } - mController.removeRemoteInput(mEntry, mToken); + mController.removeRemoteInput(mEntry, mToken, + /* reason= */"RemoteInputView#onDetachedFromWindow"); mController.removeSpinning(mEntry.getKey(), mToken); } @@ -655,7 +657,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setText(mEntry.remoteInputText); mEditText.setSelection(mEditText.length()); mEditText.requestFocus(); - mController.addRemoteInput(mEntry, mToken); + mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus"); setAttachment(mEntry.remoteInputAttachment); updateSendButton(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index a50fd6f86e09..6c0d43394074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -255,7 +255,8 @@ class RemoteInputViewControllerImpl @Inject constructor( entry.lastRemoteInputSent = SystemClock.elapsedRealtime() entry.mRemoteEditImeAnimatingAway = true remoteInputController.addSpinning(entry.key, view.mToken) - remoteInputController.removeRemoteInput(entry, view.mToken) + remoteInputController.removeRemoteInput(entry, view.mToken, + /* reason= */ "RemoteInputViewController#sendRemoteInput") remoteInputController.remoteInputSent(entry) entry.setHasSentReply() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt index 80f3d76f0897..96717c7542d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -38,7 +38,8 @@ interface BluetoothRepository { /** * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once * those statuses have been fetched. The fetching occurs on a background thread because IPCs may - * be required to fetch the statuses (see b/271058380). + * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in + * the main thread. */ fun fetchConnectionStatusInBackground( currentDevices: Collection<CachedBluetoothDevice>, diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 760fe6a96fda..f5edb7bb5b73 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -42,6 +42,7 @@ public class Utils { * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be * used; try using `synchronized` or making a copy of the list instead. */ + @Deprecated public static <T> void safeForeach(List<T> list, Consumer<T> c) { for (int i = list.size() - 1; i >= 0; i--) { T item = list.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java new file mode 100644 index 000000000000..855bba6cfd24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java @@ -0,0 +1,35 @@ +/* + * 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.util.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Descriptive annotation for clearly tagging callback types that are weakly + * referenced during registration. + * + * This is useful in providing hints to Proguard about certain fields that + * should be kept to preserve strong references. + */ +@Retention(RetentionPolicy.CLASS) +@Target({TYPE}) +public @interface WeaklyReferencedCallback {} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt index 73e2f97d92ae..ffbc10aa5f59 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt @@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin class Utils { companion object { fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) + fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c) fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 9b06a37e681f..d56672564f6f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -191,7 +191,7 @@ public class AsyncSensorManager extends SensorManager } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { throw new UnsupportedOperationException("not implemented"); } diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java index 968dcc95ef50..df5162af70c5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java @@ -26,6 +26,7 @@ import android.util.Log; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,6 +65,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * An interface for listening to the connection status. * @param <T> The wrapper type. */ + @WeaklyReferencedCallback public interface Callback<T> { /** * Invoked when the service has been successfully connected to. diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java index 768743217cc7..425336d540f5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java @@ -16,6 +16,8 @@ package com.android.systemui.util.service; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + /** * The {@link Observer} interface specifies an entity which listeners * can be informed of changes to the source, which will require updating. Note that this deals @@ -25,6 +27,7 @@ public interface Observer { /** * Callback for receiving updates from the {@link Observer}. */ + @WeaklyReferencedCallback interface Callback { /** * Invoked when the source has changed. diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 6afa5256523c..4d8768f5e9e0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -25,9 +25,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController @@ -47,8 +49,8 @@ import com.android.systemui.util.mockito.mock import java.util.TimeZone import java.util.concurrent.Executor import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.yield import org.junit.Assert.assertEquals import org.junit.Before @@ -90,10 +92,10 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var smallClockEvents: ClockFaceEvents @Mock private lateinit var largeClockEvents: ClockFaceEvents @Mock private lateinit var parentView: View - @Mock private lateinit var transitionRepository: KeyguardTransitionRepository private lateinit var repository: FakeKeyguardRepository @Mock private lateinit var smallLogBuffer: LogBuffer @Mock private lateinit var largeLogBuffer: LogBuffer + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var underTest: ClockEventController @Before @@ -125,17 +127,13 @@ class ClockEventControllerTest : SysuiTestCase() { withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) - set(Flags.DOZING_MIGRATION_1, false) + set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) set(Flags.FACE_AUTH_REFACTOR, false) } underTest = ClockEventController( withDeps.keyguardInteractor, - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - featureFlags = withDeps.featureFlags, - ) - .keyguardTransitionInteractor, + keyguardTransitionInteractor, broadcastDispatcher, batteryController, keyguardUpdateMonitor, @@ -316,6 +314,68 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + fun listenForDozeAmountTransition_updatesClockDozeAmount() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep) + + val job = underTest.listenForDozeAmountTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0.4f + ) + yield() + + verify(animations, times(2)).doze(0.4f) + + job.cancel() + } + + @Test + fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToAodTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + yield() + + verify(animations, times(2)).doze(1f) + + job.cancel() + } + + @Test + fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToAodTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + yield() + + verify(animations, never()).doze(1f) + + job.cancel() + } + + @Test fun unregisterListeners_validate() = runBlocking(IMMEDIATE) { underTest.unregisterListeners() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 3b8e02f7455a..22c75d85d4a1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,6 +33,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.statusbar.notification.AnimatableProperty; @@ -60,6 +63,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected FeatureFlags mFeatureFlags; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; protected FakePowerRepository mFakePowerRepository; @@ -90,6 +94,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, mInteractionJankMonitor, deps.getKeyguardInteractor(), + mKeyguardTransitionInteractor, mDumpManager, PowerInteractorFactory.create( mFakePowerRepository @@ -105,8 +110,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { }; when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); - when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); + when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow()); } protected void givenViewAttached() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 3da72618fb60..5346db1aa067 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -87,6 +90,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); MockitoAnnotations.initMocks(this); mContextWrapper = new ContextWrapper(mContext) { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java index fd258e38a00f..3b2ea0f5ce86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java @@ -16,6 +16,9 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -56,6 +59,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 3a8bcd0ec2f0..76a3153e648a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -16,6 +16,9 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; @@ -75,6 +78,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); @@ -96,6 +100,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase { Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, mLastIsMoveToTucked); mEndListenerCaptor.getAllValues().clear(); + mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java index 9b819479ec70..83bcd8d50407 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java @@ -16,6 +16,9 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import android.content.res.Resources; @@ -43,6 +46,7 @@ public class MenuEduTooltipViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager); mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index 5764839d160d..e01f1b76df79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; @@ -72,6 +75,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Before public void setUp() { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index 98be49f9d493..a88ee108141b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.View.OVER_SCROLL_NEVER; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyFloat; @@ -33,6 +36,7 @@ import android.view.MotionEvent; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.recyclerview.widget.RecyclerView; import androidx.test.filters.SmallTest; @@ -79,6 +83,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); @@ -213,5 +218,6 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @After public void tearDown() { mMotionEventHelper.recycleEvents(); + mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index 31824ecaa432..41e5c209e344 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.systemBars; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -73,6 +76,7 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final WindowManager wm = mContext.getSystemService(WindowManager.class); doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( mWindowManager).getMaximumWindowMetrics(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 5bb5e01675be..b0776c9a5b58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -22,7 +22,9 @@ import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static com.google.common.truth.Truth.assertThat; @@ -50,6 +52,7 @@ import android.view.WindowManager; import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; +import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -110,6 +113,7 @@ public class MenuViewLayerTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); final Rect mDisplayBounds = new Rect(); mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_HEIGHT); @@ -145,6 +149,7 @@ public class MenuViewLayerTest extends SysuiTestCase { UserHandle.USER_CURRENT); mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); + mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel); mMenuViewLayer.onDetachedFromWindow(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 5cd0fd0bb42d..ac2bfaf8eddf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -67,6 +70,7 @@ public class MenuViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { + setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); mUiModeManager = mContext.getSystemService(UiModeManager.class); mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); 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 712eef13421b..000000000000 --- 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 000000000000..99501c426e0c --- /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/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 3df9cbb29e4a..91409a376556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -11,11 +11,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalWidgetMetadata +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -59,9 +62,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var logBuffer: LogBuffer private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before @@ -71,6 +77,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create() featureFlagEnabled(true) + communalRepository = FakeCommunalRepository() + communalRepository.setIsCommunalEnabled(true) + + overrideResource( + R.array.config_communalWidgetAllowlist, + arrayOf(componentName1, componentName2) + ) + whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(userTracker.userHandle).thenReturn(userHandle) } @@ -219,11 +233,36 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { Mockito.verify(appWidgetHost).stopListening() } + @Test + fun getCommunalWidgetAllowList_onInit() { + testScope.runTest { + val repository = initCommunalWidgetRepository() + val communalWidgetAllowlist = repository.communalWidgetAllowlist + assertThat( + listOf( + CommunalWidgetMetadata( + componentName = componentName1, + priority = 2, + sizes = listOf(CommunalContentSize.HALF) + ), + CommunalWidgetMetadata( + componentName = componentName2, + priority = 1, + sizes = listOf(CommunalContentSize.HALF) + ) + ) + ) + .containsExactly(*communalWidgetAllowlist.toTypedArray()) + } + } + private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl { return CommunalWidgetRepositoryImpl( + context, appWidgetManager, appWidgetHost, broadcastDispatcher, + communalRepository, packageManager, userManager, userTracker, @@ -282,4 +321,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private fun installedProviders(providers: List<AppWidgetProviderInfo>) { whenever(appWidgetManager.installedProviders).thenReturn(providers) } + + companion object { + const val componentName1 = "component name 1" + const val componentName2 = "component name 2" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index 41a8be9663b7..33a666700877 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt @@ -6,6 +6,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import org.junit.Before import org.junit.Test @@ -18,6 +19,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class DefaultCommunalBlueprintTest : SysuiTestCase() { + @Mock private lateinit var hubSection: DefaultCommunalHubSection @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection private lateinit var blueprint: DefaultCommunalBlueprint @@ -25,13 +27,14 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - blueprint = DefaultCommunalBlueprint(widgetSection) + blueprint = DefaultCommunalBlueprint(hubSection, widgetSection) } @Test fun addView() { val constraintLayout = ConstraintLayout(context, null) blueprint.replaceViews(null, constraintLayout) + verify(hubSection).addViews(constraintLayout) verify(widgetSection).addViews(constraintLayout) } @@ -39,6 +42,7 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() blueprint.applyConstraints(cs) + verify(hubSection).applyConstraints(cs) verify(widgetSection).applyConstraints(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt new file mode 100644 index 000000000000..9b8e581d1ba4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt @@ -0,0 +1,195 @@ +/* + * 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.deviceentry.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricUnlockLogger +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.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryHapticsInteractorTest : SysuiTestCase() { + + private lateinit var repository: DeviceEntryHapticsRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private lateinit var keyEventRepository: FakeKeyEventRepository + private lateinit var powerRepository: FakePowerRepository + private lateinit var systemClock: FakeSystemClock + private lateinit var underTest: DeviceEntryHapticsInteractor + + @Before + fun setUp() { + repository = DeviceEntryHapticsRepositoryImpl() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + keyEventRepository = FakeKeyEventRepository() + powerRepository = FakePowerRepository() + systemClock = FakeSystemClock() + underTest = + DeviceEntryHapticsInteractor( + repository = repository, + fingerprintPropertyRepository = fingerprintPropertyRepository, + biometricSettingsRepository = biometricSettingsRepository, + keyEventInteractor = KeyEventInteractor(keyEventRepository), + powerInteractor = + PowerInteractor( + powerRepository, + mock(FalsingCollector::class.java), + mock(ScreenOffAnimationController::class.java), + mock(StatusBarStateController::class.java), + ), + systemClock = systemClock, + logger = mock(BiometricUnlockLogger::class.java), + ) + } + + @Test + fun nonPowerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's only been 50ms since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun nonPowerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) + underTest.vibrateError() + assertThat(playErrorHaptic).isFalse() + } + + private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = fingerprintSensorType, + sensorLocations = mapOf(), + ) + } + + private fun setPowerButtonFingerprintProperty() { + setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON) + } + + private fun setFingerprintEnrolled() { + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + } + + private fun setAwakeFromPowerButton() { + powerRepository.updateWakefulness( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + powerButtonLaunchGestureTriggered = false, + ) + } +} 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 37c70d8f25e0..2bd2bff80951 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 c12a581fb2c1..f51745b9f17c 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/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt new file mode 100644 index 000000000000..bb6786ade21a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt @@ -0,0 +1,45 @@ +/* + * 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.flags + +import android.platform.test.flag.junit.SetFlagsRule + +fun SetFlagsRule.setFlagDefault(flagName: String) { + if (getFlagDefault(flagName)) { + enableFlags(flagName) + } else { + disableFlags(flagName) + } +} + +// NOTE: This code uses reflection to gain access to private members of aconfig generated +// classes (in the same way SetFlagsRule does internally) because this is the only way to get +// at the underlying information and read the current value of the flag. +// If aconfig had flag constants with accessible default values, this would be unnecessary. +private fun getFlagDefault(name: String): Boolean { + val flagPackage = name.substringBeforeLast(".") + val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl") + val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance() + val flagMethodName = name.substringAfterLast(".").snakeToCamelCase() + val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName) + return flagGetter.invoke(featureFlagsImpl) as Boolean +} + +private fun String.snakeToCamelCase(): String { + val pattern = "_[a-z]".toRegex() + return replace(pattern) { it.value.last().uppercase() } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt index 0ee348e0d23d..7750d25de753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import kotlin.math.max +import kotlin.test.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -149,26 +151,52 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { } @Test + fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() { + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event smaller than the progress threshold + val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f) + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) + clock.advanceTime(dragTextureThresholdMillis.toLong()) + sliderHapticFeedbackProvider.onProgress(secondProgress) + + // THEN Only the first compositions plays + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java)) + } + + @Test fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() { - // GIVEN max velocity and slider progress - val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) - val ticks = VibrationEffect.startComposition() - repeat(config.numberOfLowTicks) { - ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) - } + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event beyond progress threshold + val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f + val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress) // GIVEN system running for 1s clock.advanceTime(1000) - // WHEN two calls to play occur with the required threshold separation - sliderHapticFeedbackProvider.onProgress(progress) + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) clock.advanceTime(dragTextureThresholdMillis.toLong()) - sliderHapticFeedbackProvider.onProgress(progress) + sliderHapticFeedbackProvider.onProgress(secondProgress) - // THEN the correct composition plays two times - verify(vibratorHelper, times(2)) - .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java)) + // THEN the correct compositions play + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(eq(secondTicks), any(VibrationAttributes::class.java)) } @Test @@ -229,6 +257,38 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java)) } + fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // THEN the dragTextureLastProgress remembers the latest progress + assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + + @Test + fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // WHEN the handle is released + sliderHapticFeedbackProvider.onHandleReleasedFromTouch() + + // THEN the dragTextureLastProgress tracker is reset + assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + private fun scaleAtBookends(velocity: Float): Float { val range = config.upperBookendScale - config.lowerBookendScale val interpolatedVelocity = @@ -244,4 +304,15 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { val bump = interpolatedVelocity * config.additionalVelocityMaxBump return interpolatedProgress * range + config.progressBasedDragMinScale + bump } + + private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect { + val ticks = VibrationEffect.startComposition() + repeat(config.numberOfLowTicks) { + ticks.addPrimitive( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + scaleAtProgressChange(velocity, progress) + ) + } + return ticks.compose() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index d8cdf29284ed..90fd6523ec12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -147,7 +147,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { emptyMap() ) verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) - verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture()) + verify(authController, times(2)).addCallback(authControllerCallback.capture()) } @Test @@ -314,18 +314,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() = testScope.runTest { createBiometricSettingsRepository() + val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + faceAuthIsEnabledByBiometricManager() doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID) runCurrent() - clearInvocations(authController) - whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false) - val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + enrollmentChange(FACE, PRIMARY_USER_ID, false) assertThat(faceAuthAllowed()).isFalse() - verify(authController).addCallback(authControllerCallback.capture()) + enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true) assertThat(faceAuthAllowed()).isFalse() @@ -375,25 +375,20 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { fun faceEnrollmentChangesArePropagatedAfterUserSwitch() = testScope.runTest { createBiometricSettingsRepository() + val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + verify(biometricManager) .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture()) userRepository.setSelectedUserInfo(ANOTHER_USER) doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID) biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) - - runCurrent() - clearInvocations(authController) - - val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) - runCurrent() - - verify(authController).addCallback(authControllerCallback.capture()) - + onNonStrongAuthChanged(true, ANOTHER_USER_ID) whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true) enrollmentChange(FACE, ANOTHER_USER_ID, true) + runCurrent() - assertThat(faceAuthAllowed()).isTrue() + assertThat(faceAuthAllowed).isTrue() } @Test @@ -637,6 +632,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed) faceAuthIsEnrolled() + enrollmentChange(FACE, PRIMARY_USER_ID, true) deviceIsInPostureThatSupportsFaceAuth() doNotDisableKeyguardAuthFeatures() faceAuthIsStrongBiometric() @@ -660,6 +656,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed) faceAuthIsEnrolled() + enrollmentChange(FACE, PRIMARY_USER_ID, true) deviceIsInPostureThatSupportsFaceAuth() doNotDisableKeyguardAuthFeatures() faceAuthIsNonStrongBiometric() @@ -753,7 +750,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { - authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) + authControllerCallback.allValues.forEach { + it.onEnrollmentsChanged(biometricType, userId, enabled) + } } private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 06eb0dd364b5..6ad2d731f375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -43,7 +43,7 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry -import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -95,6 +95,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { FakeDeviceEntryFingerprintAuthRepository private lateinit var fakeKeyguardRepository: FakeKeyguardRepository private lateinit var powerInteractor: PowerInteractor + private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig @@ -123,6 +124,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { facePropertyRepository = FakeFacePropertyRepository() fakeKeyguardRepository = FakeKeyguardRepository() powerInteractor = PowerInteractorFactory.create().powerInteractor + fakeBiometricSettingsRepository = FakeBiometricSettingsRepository() + underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -147,7 +150,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { mock(StatusBarStateController::class.java), mock(KeyguardStateController::class.java), bouncerRepository, - mock(BiometricSettingsRepository::class.java), + fakeBiometricSettingsRepository, FakeSystemClock(), keyguardUpdateMonitor, ), @@ -160,6 +163,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { facePropertyRepository, faceWakeUpTriggersConfig, powerInteractor, + fakeBiometricSettingsRepository, ) } @@ -481,6 +485,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest { underTest.start() + fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 2cf0e77b5994..5d5ece0f8dcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -433,7 +433,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -457,7 +459,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN biometrics succeeds with wake and unlock from dream mode keyguardRepository.setBiometricUnlockState( @@ -487,7 +491,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the primary bouncer is set to show bouncerRepository.setPrimaryShow(true) @@ -515,7 +521,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the device begins to sleep keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -547,7 +555,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the keyguard is occluded and the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -783,7 +793,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // WHEN the alternateBouncer stops showing and then the primary bouncer shows bouncerRepository.setPrimaryShow(true) @@ -808,7 +820,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod available and starting to sleep bouncerRepository.setPrimaryShow(false) @@ -838,7 +852,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep // to sleep @@ -869,7 +885,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) @@ -980,7 +998,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER) + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.PRIMARY_BOUNCER + ) // WHEN the primary bouncer stops showing and lockscreen hosted dream still active bouncerRepository.setPrimaryShow(false) @@ -1161,6 +1181,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the keyguard is occluded and device wakes up and is no longer dreaming + keyguardRepository.setDreaming(false) + keyguardRepository.setKeyguardOccluded(true) + powerInteractor.setAwakeForTest() + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun aodToOccluded() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1286,8 +1357,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } private suspend fun TestScope.runTransitionAndSetWakefulness( - from: KeyguardState, - to: KeyguardState + from: KeyguardState, + to: KeyguardState ) { transitionRepository.sendTransitionStep( TransitionStep( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..255f4df17244 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -0,0 +1,112 @@ +/* + * 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.keyguard.ui.viewmodel + +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.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.collect.Range +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.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GoneToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: GoneToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var testScope: TestScope + + @Before + fun setUp() { + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = GoneToAodTransitionViewModel(interactor) + } + + @Test + fun enterFromTopTranslationY() = + testScope.runTest { + val pixels = -100f + val enterFromTopTranslationY by + collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) + + // The animation should only start > halfway through + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(enterFromTopTranslationY).isEqualTo(pixels) + + repository.sendTransitionStep(step(0.5f)) + assertThat(enterFromTopTranslationY).isEqualTo(pixels) + + repository.sendTransitionStep(step(.85f)) + assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f)) + + // At the end, the translation should be complete and set to zero + repository.sendTransitionStep(step(1f)) + assertThat(enterFromTopTranslationY).isEqualTo(0f) + } + + @Test + fun enterFromTopAnimationAlpha() = + testScope.runTest { + val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) + + // The animation should only start > halfway through + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f)) + + repository.sendTransitionStep(step(1f)) + assertThat(enterFromTopAnimationAlpha).isEqualTo(1f) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "GoneToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 71688db76a8b..4f545cb0e288 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -26,12 +28,17 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.ClockController import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import javax.inject.Provider +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -41,6 +48,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock +import org.mockito.Mockito.anyInt import org.mockito.MockitoAnnotations @SmallTest @@ -51,10 +59,20 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private lateinit var testScope: TestScope private lateinit var repository: FakeKeyguardRepository private lateinit var keyguardInteractor: KeyguardInteractor + private lateinit var configurationRepository: FakeConfigurationRepository @Mock private lateinit var burnInInteractor: BurnInInteractor + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel + @Mock + private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController private val burnInFlow = MutableStateFlow(BurnInModel()) + private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0) + private val enterFromTopAnimationAlpha = MutableStateFlow(0f) + private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) + private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) + private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE) @Before fun setUp() { @@ -71,9 +89,30 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) keyguardInteractor = withDeps.keyguardInteractor repository = withDeps.repository + configurationRepository = withDeps.configurationRepository + + whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) + .thenReturn(emptyFlow<Float>()) + whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) + .thenReturn(enterFromTopAnimationAlpha) whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) - underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor) + + whenever(keyguardTransitionInteractor.goneToAodTransition) + .thenReturn(goneToAodTransitionStep) + whenever(keyguardTransitionInteractor.dozeAmountTransition) + .thenReturn(dozeAmountTransitionStep) + whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState) + + underTest = + KeyguardRootViewModel( + context, + keyguardInteractor, + burnInInteractor, + goneToAodTransitionViewModel, + aodToLockscreenTransitionViewModel, + keyguardTransitionInteractor, + ) underTest.clockControllerProvider = Provider { clockController } } @@ -118,7 +157,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val scale by collectLastValue(underTest.scale) // Set to not dozing (on lockscreen) - repository.setDozeAmount(0f) + dozeAmountTransitionStep.emit(TransitionStep(value = 0f)) // Trigger a change to the burn-in model burnInFlow.value = @@ -141,8 +180,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val scale by collectLastValue(underTest.scale) // Set to dozing (on AOD) - repository.setDozeAmount(1f) - + dozeAmountTransitionStep.emit(TransitionStep(value = 1f)) // Trigger a change to the burn-in model burnInFlow.value = BurnInModel( @@ -150,10 +188,15 @@ class KeyguardRootViewModelTest : SysuiTestCase() { translationY = 30, scale = 0.5f, ) - assertThat(translationX).isEqualTo(20) assertThat(translationY).isEqualTo(30) assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) + + // Set to the beginning of GONE->AOD transition + goneToAodTransitionStep.emit(TransitionStep(value = 0f)) + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) } @Test @@ -166,7 +209,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val scale by collectLastValue(underTest.scale) // Set to dozing (on AOD) - repository.setDozeAmount(1f) + dozeAmountTransitionStep.emit(TransitionStep(value = 1f)) // Trigger a change to the burn-in model burnInFlow.value = @@ -180,4 +223,28 @@ class KeyguardRootViewModelTest : SysuiTestCase() { assertThat(translationY).isEqualTo(0) assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */)) } + + @Test + fun burnInLayerVisibility() = + testScope.runTest { + val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility) + + startedKeyguardState.value = KeyguardState.OCCLUDED + assertThat(burnInLayerVisibility).isNull() + + startedKeyguardState.value = KeyguardState.AOD + assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE) + } + + @Test + fun burnInLayerAlpha() = + testScope.runTest { + val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha) + + enterFromTopAnimationAlpha.value = 0.2f + assertThat(burnInLayerAlpha).isEqualTo(0.2f) + + enterFromTopAnimationAlpha.value = 1f + assertThat(burnInLayerAlpha).isEqualTo(1f) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index 85d3fbad5a6e..deefab670c71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice -import com.android.systemui.res.R +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq @@ -95,6 +99,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() + private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, - dumpster + dumpster, + featureFlags, ) manager.addListener(listener) @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { + fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session name is null, and is a system session for a PhoneMediaDevice + val phoneDevice = mock(PhoneMediaDevice::class.java) + whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) + whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) + whenever(route.isSystemSession).thenReturn(true) + + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the PhoneMediaDevice string + val data = captureDeviceData(KEY) + assertThat(data.name) + .isEqualTo( + context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) + ) + } + + @Test + fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session does not have a name, and is a system session + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(route.isSystemSession).thenReturn(true) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the selected route name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } - fun captureCallback(): LocalMediaManager.DeviceCallback { + // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled + + @Test + fun loadMediaDataWithNullToken_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + // Ensure we'll get device info when using the address + val fullMediaDevice = mock(MediaDevice::class.java) + val address = "fakeAddress" + val nameFromDevice = "nameFromDevice" + val iconFromDevice = mock(Drawable::class.java) + whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) + whenever(fullMediaDevice.name).thenReturn(nameFromDevice) + whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) + + // WHEN the about-to-connect device changes to non-null + val deviceCallback = captureCallback() + val nameFromParam = "nameFromParam" + val iconFromParam = mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + + // THEN the about-to-connect device based on the address is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(nameFromDevice) + assertThat(data.name).isNotEqualTo(nameFromParam) + assertThat(data.icon).isEqualTo(iconFromDevice) + assertThat(data.icon).isNotEqualTo(iconFromParam) + } + + @Test + fun deviceNameFromMR2RouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a valid routing session + whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN it uses the route name (instead of device name) + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns null for routing session + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is disabled and name is set to null + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isNull() + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a routing session that does not have a name + whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is enabled and uses the current connected device name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.enabled).isTrue() + } + + // End duplicate tests + + private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } - fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { + private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } - fun setupLeAudioConfiguration(isLeAudio: Boolean) { + private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } - fun setupBroadcastPackage(currentName: String) { + private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } - fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { + private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index f25cd24dfcb0..6cdf4efd67da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -4,18 +4,25 @@ import android.content.ComponentName import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.never import org.mockito.Mockito.verify @RunWith(AndroidTestingRunner::class) @@ -33,8 +40,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val logger = mock<MediaProjectionMetricsLogger>() - private val controller = + private val thumbnailLoader = FakeThumbnailLoader() + + private fun createController(isFirstStart: Boolean = true) = MediaProjectionAppSelectorController( taskListProvider, view, @@ -42,7 +52,10 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { personalUserHandle, scope, appSelectorComponentName, - callerPackageName + callerPackageName, + thumbnailLoader, + isFirstStart, + logger ) @Before @@ -54,7 +67,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initNoRecentTasks_bindsEmptyList() { taskListProvider.tasks = emptyList() - controller.init() + createController().init() verify(view).bind(emptyList()) } @@ -63,12 +76,28 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initOneRecentTask_bindsList() { taskListProvider.tasks = listOf(createRecentTask(taskId = 1)) - controller.init() + createController().init() verify(view).bind(listOf(createRecentTask(taskId = 1))) } @Test + fun init_refreshesThumbnailsOfForegroundTasks() = runTest { + val tasks = + listOf( + createRecentTask(taskId = 1, isForegroundTask = false), + createRecentTask(taskId = 2, isForegroundTask = true), + createRecentTask(taskId = 3, isForegroundTask = true), + createRecentTask(taskId = 4, isForegroundTask = false), + ) + taskListProvider.tasks = tasks + + createController().init() + + assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) + } + + @Test fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() { val tasks = listOf( @@ -78,7 +107,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -101,7 +130,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -124,7 +153,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -149,7 +178,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -176,11 +205,29 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { taskListProvider.tasks = tasks givenCaptureAllowed(isAllow = false) - controller.init() + createController().init() verify(view).bind(emptyList()) } + @Test + fun init_firstStart_logsAppSelectorDisplayed() { + val controller = createController(isFirstStart = true) + + controller.init() + + verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + + @Test + fun init_notFirstStart_doesNotLogAppSelectorDisplayed() { + val controller = createController(isFirstStart = false) + + controller.init() + + verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + private fun givenCaptureAllowed(isAllow: Boolean) { whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow) } @@ -188,14 +235,17 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private fun createRecentTask( taskId: Int, topActivityComponent: ComponentName? = null, - userId: Int = personalUserHandle.identifier + userId: Int = personalUserHandle.identifier, + isForegroundTask: Boolean = false ): RecentTask { return RecentTask( taskId = taskId, + displayId = 0, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, - colorBackground = 0 + colorBackground = 0, + isForegroundTask = isForegroundTask, ) } @@ -205,4 +255,18 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { override suspend fun loadRecentTasks(): List<RecentTask> = tasks } + + private class FakeThumbnailLoader : RecentTaskThumbnailLoader { + + val capturedTaskIds = mutableListOf<Int>() + + override suspend fun loadThumbnail(taskId: Int): ThumbnailData? { + return null + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? { + capturedTaskIds += taskId + return null + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt new file mode 100644 index 000000000000..db275ec190ac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -0,0 +1,109 @@ +package com.android.systemui.mediaprojection.appselector.data + +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.res.Configuration +import android.graphics.ColorSpace +import android.graphics.Point +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.testing.AndroidTestingRunner +import android.view.Surface +import android.window.TaskSnapshot +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + private val activityManager = mock<ActivityManagerWrapper>() + private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager) + + @Test + fun loadThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val thumbnailData = ThumbnailData() + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isNull() + } + + @Test + fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val snapshot = createTaskSnapshot() + val thumbnailData = ThumbnailData(snapshot) + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData) + } + + @Test + fun captureThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 321 + val emptyThumbnailData = ThumbnailData() + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isNull() + } + + @Test + fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 321 + val thumbnailData = ThumbnailData(createTaskSnapshot()) + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData) + } + + private fun createTaskSnapshot() = + TaskSnapshot( + /* id= */ 123, + /* captureTime= */ 0, + /* topActivityComponent= */ ComponentName("package", "class"), + /* snapshot= */ HardwareBuffer.create( + /* width= */ 100, + /* height= */ 100, + HardwareBuffer.RGBA_8888, + /* layers= */ 1, + /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN + ), + ColorSpace.get(ColorSpace.Named.SRGB), + Configuration.ORIENTATION_PORTRAIT, + Surface.ROTATION_0, + /* taskSize= */ Point(100, 100), + /* contentInsets= */ Rect(), + /* letterboxInsets= */ Rect(), + /* isLowResolution= */ false, + /* isRealSnapshot= */ true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + /* appearance= */ 0, + /* isTranslucent= */ false, + /* hasImeSurface= */ false + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index d35a21236ae8..d75553fe57ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -11,7 +11,7 @@ import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks import com.android.wm.shell.util.GroupedRecentTaskInfo import com.google.common.truth.Truth.assertThat -import java.util.* +import java.util.Optional import java.util.function.Consumer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,12 +52,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder() } @Test @@ -66,8 +61,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2)) + assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder() } @Test @@ -81,15 +75,46 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - createRecentTask(taskId = 4), - createRecentTask(taskId = 5), - createRecentTask(taskId = 6), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder() + } + + @Test + fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result[0].isForegroundTask).isFalse() + } + + @Test + fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() { + givenRecentTasks( + createSingleTask(taskId = 1), + createSingleTask(taskId = 2), + createSingleTask(taskId = 3), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder() + } + + @Test + fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3), + createSingleTask(taskId = 4), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, true, true, false) + .inOrder() } @Suppress("UNCHECKED_CAST") @@ -103,10 +128,12 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { private fun createRecentTask(taskId: Int): RecentTask = RecentTask( taskId = taskId, + displayId = 0, userId = 0, topActivityComponent = null, baseIntentComponent = null, - colorBackground = null + colorBackground = null, + isForegroundTask = false, ) private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo = 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 e537131bad01..4c5a2144941a 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,25 +195,41 @@ 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(); CastController.CastDevice device = new CastController.CastDevice(); @@ -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 58e36be2b9fd..10c7c439e5f4 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 61dd69a8126b..88a5c17f4994 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/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 6e6833dc0944..90d2e78a411f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -17,8 +17,10 @@ package com.android.systemui.screenrecord; import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -37,6 +39,8 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -76,6 +80,8 @@ public class RecordingControllerTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private UserTracker mUserTracker; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private FakeFeatureFlags mFeatureFlags; private RecordingController mController; @@ -86,8 +92,15 @@ public class RecordingControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mFeatureFlags = new FakeFeatureFlags(); - mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext, - mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker); + mController = new RecordingController( + mMainExecutor, + mBroadcastDispatcher, + mContext, + mFeatureFlags, + mUserContextProvider, + () -> mDevicePolicyResolver, + mUserTracker, + mMediaProjectionMetricsLogger); mController.addCallback(mCallback); } @@ -269,4 +282,21 @@ public class RecordingControllerTest extends SysuiTestCase { assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class); } + + @Test + public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + + mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + verify(mMediaProjectionMetricsLogger) + .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 3ae1f35b8134..da4dc850fcae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before import org.junit.Test @@ -111,6 +112,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { } @Test + fun showDialog_singleAppIsDefault() { + dialog.show() + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) + val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app) + assertEquals(spinner.adapter.getItem(0), singleApp) + } + + @Test fun showDialog_cancelClicked_dialogIsDismissed() { dialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt index 0d694ee80def..7e41745936f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -28,7 +28,6 @@ import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import com.android.internal.util.ScreenshotRequest -import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -47,7 +46,6 @@ class RequestProcessorTest { private val scope = CoroutineScope(Dispatchers.Unconfined) private val policy = FakeScreenshotPolicy() - private val flags = FakeFeatureFlags() /** Tests the Java-compatible function wrapper, ensures callback is invoked. */ @Test @@ -58,7 +56,7 @@ class RequestProcessorTest { .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) .build() ) - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) var result: ScreenshotData? = null var callbackCount = 0 @@ -86,7 +84,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -111,7 +109,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -138,7 +136,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) Assert.assertThrows(IllegalStateException::class.java) { runBlocking { processor.process(ScreenshotData.fromRequest(request)) } @@ -148,7 +146,7 @@ class RequestProcessorTest { @Test fun testProvidedImageScreenshot() = runBlocking { val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) policy.setManagedProfile(USER_ID, false) @@ -173,7 +171,7 @@ class RequestProcessorTest { @Test fun testProvidedImageScreenshot_managedProfile() = runBlocking { val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy, flags, scope) + val processor = RequestProcessor(imageCapture, policy, scope) // Indicate that the screenshot belongs to a manged profile policy.setManagedProfile(USER_ID, true) 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 a105c15b630a..3dc90374206a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import java.lang.IllegalStateException import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent @@ -43,8 +44,11 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { private val controller0 = mock<ScreenshotController>() private val controller1 = mock<ScreenshotController>() + private val notificationsController0 = mock<ScreenshotNotificationsController>() + private val notificationsController1 = mock<ScreenshotNotificationsController>() private val controllerFactory = mock<ScreenshotController.Factory>() private val callback = mock<TakeScreenshotService.RequestCallback>() + private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>() private val fakeDisplayRepository = FakeDisplayRepository() private val requestProcessor = FakeRequestProcessor() @@ -59,12 +63,15 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope, requestProcessor, eventLogger, + notificationControllerFactory ) @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) + whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0) + whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1) } @Test @@ -74,8 +81,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 +114,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 +146,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), @@ -310,6 +317,123 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.onDestroy() } + @Test + fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + requestProcessor.shouldThrowException = true + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + val screenshotRequested = + eventLogger.logs.filter { + it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id + } + assertThat(screenshotRequested).hasSize(2) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromProcessor_logsUiError() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + requestProcessor.shouldThrowException = true + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + val screenshotRequested = + eventLogger.logs.filter { + it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id + } + assertThat(screenshotRequested).hasSize(2) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + requestProcessor.shouldThrowException = true + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + verify(notificationsController0).notifyScreenshotError(any()) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0)) + val onSaved = { _: Uri -> } + requestProcessor.shouldThrowException = true + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + verify(notificationsController0).notifyScreenshotError(any()) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromScreenshotController_reportsRequested() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + whenever(controller0.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + whenever(controller1.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + val screenshotRequested = + eventLogger.logs.filter { + it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id + } + assertThat(screenshotRequested).hasSize(2) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromScreenshotController_reportsError() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + whenever(controller0.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + whenever(controller1.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + val screenshotRequested = + eventLogger.logs.filter { + it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id + } + assertThat(screenshotRequested).hasSize(2) + screenshotExecutor.onDestroy() + } + + @Test + fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() = + testScope.runTest { + setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val onSaved = { _: Uri -> } + whenever(controller0.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + whenever(controller1.handleScreenshot(any(), any(), any())) + .thenThrow(IllegalStateException::class.java) + + screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + + verify(notificationsController0).notifyScreenshotError(any()) + verify(notificationsController1).notifyScreenshotError(any()) + screenshotExecutor.onDestroy() + } + private suspend fun TestScope.setDisplays(vararg displays: Display) { fakeDisplayRepository.emit(displays.toSet()) runCurrent() @@ -328,8 +452,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { private class FakeRequestProcessor : ScreenshotRequestProcessor { var processed: ScreenshotData? = null var toReturn: ScreenshotData? = null - + var shouldThrowException = false override suspend fun process(screenshot: ScreenshotData): ScreenshotData { + if (shouldThrowException) throw RequestProcessorException("") processed = screenshot return toReturn ?: screenshot } 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 6205d90197a2..f3809aad4de3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -65,6 +65,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { private val requestProcessor = mock<RequestProcessor>() private val devicePolicyManager = mock<DevicePolicyManager>() private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() + private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>() private val notificationsController = mock<ScreenshotNotificationsController>() private val callback = mock<RequestCallback>() @@ -86,7 +87,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any())).thenReturn(controller) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) + whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController) // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { @@ -323,7 +325,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { userManager, devicePolicyManager, eventLogger, - notificationsController, + notificationsControllerFactory, mContext, Runnable::run, flags, 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 8d8c70e26ab2..6223e250d603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -157,6 +157,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; @@ -328,6 +329,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private CastController mCastController; @Mock private KeyguardRootView mKeyguardRootView; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; protected final int mMaxUdfpsBurnInOffsetY = 5; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @@ -388,6 +390,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, mInteractionJankMonitor, mKeyguardInteractor, + mKeyguardTransitionInteractor, mDumpManager, mPowerInteractor)); @@ -667,7 +670,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/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 607cdab12f56..df38f936f4f2 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 c4237827c3ea..3064f4b2b355 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/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt deleted file mode 100644 index e4da53a1a0a4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import com.google.common.truth.Truth.assertThat - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Point -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -private const val WIDTH = 200 -private const val HEIGHT = 200 - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class MediaArtworkProcessorTest : SysuiTestCase() { - - private var screenWidth = 0 - private var screenHeight = 0 - - private lateinit var processor: MediaArtworkProcessor - - @Before - fun setUp() { - processor = MediaArtworkProcessor() - - val point = Point() - checkNotNull(context.display).getSize(point) - screenWidth = point.x - screenHeight = point.y - } - - @After - fun tearDown() { - processor.clearCache() - } - - @Test - fun testProcessArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has the size of the screen that has been downsamples - assertThat(background.height).isLessThan(screenHeight) - assertThat(background.width).isLessThan(screenWidth) - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testCache() { - // GIVEN a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is processed twice - val background1 = processor.processArtwork(context, artwork)!! - val background2 = processor.processArtwork(context, artwork)!! - // THEN the two bitmaps are the same - // Note: This is currently broken and trying to use caching causes issues - assertThat(background1).isNotSameInstanceAs(background2) - } - - @Test - fun testConfig() { - // GIVEN some which is not ARGB_8888 - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has Config ARGB_8888 - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testRecycledArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // AND the artwork is recycled - artwork.recycle() - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork) - // THEN the processed bitmap is null - assertThat(background).isNull() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java new file mode 100644 index 000000000000..3a9c24a7109c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase { + @Mock + private NotificationPresenter mPresenter; + @Mock + private UserManager mUserManager; + @Mock + private UserTracker mUserTracker; + + // Dependency mocks: + @Mock + private NotificationVisibilityProvider mVisibilityProvider; + @Mock + private CommonNotifCollection mNotifCollection; + @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock + private NotificationClickNotifier mClickNotifier; + @Mock + private OverviewProxyService mOverviewProxyService; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private DeviceProvisionedController mDeviceProvisionedController; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private KeyguardStateController mKeyguardStateController; + + private UserInfo mCurrentUser; + private UserInfo mSecondaryUser; + private UserInfo mWorkUser; + private FakeSettings mSettings; + private TestNotificationLockscreenUserManager mLockscreenUserManager; + private NotificationEntry mCurrentUserNotif; + private NotificationEntry mSecondaryUserNotif; + private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + + int currentUserId = ActivityManager.getCurrentUser(); + when(mUserTracker.getUserId()).thenReturn(currentUserId); + mSettings = new FakeSettings(); + mSettings.setUserId(ActivityManager.getCurrentUser()); + mCurrentUser = new UserInfo(currentUserId, "", 0); + mSecondaryUser = new UserInfo(currentUserId + 1, "", 0); + mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0, + UserManager.USER_TYPE_PROFILE_MANAGED); + + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); + when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser)); + when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( + mSecondaryUser)); + mDependency.injectTestDependency(Dependency.MAIN_HANDLER, + Handler.createAsync(Looper.myLooper())); + + Notification notifWithPrivateVisibility = new Notification(); + notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + mCurrentUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mCurrentUser.id)) + .build(); + mSecondaryUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mSecondaryUser.id)) + .build(); + mWorkProfileNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mWorkUser.id)) + .build(); + + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mLockscreenUserManager.setUpWithPresenter(mPresenter); + } + + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + + @Test + public void testLockScreenShowNotificationsFalse() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenShowNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenAllowPrivateNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsTrue() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testCurrentUserPrivateNotificationsNotRedacted() { + // GIVEN current user doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testCurrentUserPrivateNotificationsRedacted() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testWorkPrivateNotificationsRedacted() { + // GIVEN work profile doesn't private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted() { + // GIVEN work profile allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { + // GIVEN work profile allows private notifications to show but the other users don't + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification doesn't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications do need to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testWorkProfileRedacted_otherUsersNotRedacted() { + // GIVEN work profile doesn't allow private notifications to show but the other users do + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification needs to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications don't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testSecondaryUserNotRedacted_currentUserRedacted() { + // GIVEN secondary profile allows private notifications to show but the current user + // doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the secondary profile notification still needs to be redacted because the current + // user's setting takes precedence + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testUserSwitchedCallsOnUserSwitching() { + mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, + mContext); + verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); + } + + @Test + public void testIsLockscreenPublicMode() { + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + } + + @Test + public void testUpdateIsPublicMode() { + when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + + NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); + mLockscreenUserManager.addNotificationStateChangedListener(listener); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + + // first call explicitly sets user 0 to not public; notifies + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + + // Calling again with keyguard now showing makes user 0 public; notifies + when(mKeyguardStateController.isShowing()).thenReturn(true); + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifs on lockscreen + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications_userAll() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build())); + } + + @Test + public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testHideNotifications_primary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testHideNotifications_secondary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testHideNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testShowNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_settingsChange() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + + // User disables + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + private class TestNotificationLockscreenUserManager + extends NotificationLockscreenUserManagerImpl { + public TestNotificationLockscreenUserManager(Context context) { + super( + context, + mBroadcastDispatcher, + mDevicePolicyManager, + mUserManager, + mUserTracker, + (() -> mVisibilityProvider), + (() -> mNotifCollection), + mClickNotifier, + (() -> mOverviewProxyService), + NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager, + mStatusBarStateController, + Handler.createAsync(Looper.myLooper()), + Handler.createAsync(Looper.myLooper()), + mBackgroundExecutor, + mDeviceProvisionedController, + mKeyguardStateController, + mSettings, + mock(DumpManager.class), + mock(LockPatternUtils.class), + mFakeFeatureFlags); + } + + public BroadcastReceiver getBaseBroadcastReceiverForTest() { + return mBaseBroadcastReceiver; + } + + public UserTracker.Callback getUserTrackerCallbackForTest() { + return mUserChangedCallback; + } + + public ContentObserver getLockscreenSettingsObserverForTest() { + return mLockscreenSettingsObserver; + } + + public ContentObserver getSettingsObserverForTest() { + return mSettingsObserver; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 19863ecaf723..a5f5fc7e36f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,17 +16,23 @@ package com.android.systemui.statusbar; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -38,12 +44,15 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -59,6 +68,8 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; @@ -74,12 +85,16 @@ import com.android.systemui.util.settings.FakeSettings; import com.google.android.collect.Lists; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -121,11 +136,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationEntry mCurrentUserNotif; private NotificationEntry mSecondaryUserNotif; private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + int currentUserId = ActivityManager.getCurrentUser(); when(mUserTracker.getUserId()).thenReturn(currentUserId); mSettings = new FakeSettings(); @@ -138,103 +157,144 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( mCurrentUser, mWorkUser)); + when(mUserManager.getUsers()).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser, mSecondaryUser)); when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( mSecondaryUser)); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler.createAsync(Looper.myLooper())); Notification notifWithPrivateVisibility = new Notification(); - notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE; mCurrentUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mCurrentUser.id)) .build(); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif); mSecondaryUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mSecondaryUser.id)) .build(); + mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry( + mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif); mWorkProfileNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mWorkUser.id)) .build(); + mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif); mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); mLockscreenUserManager.setUpWithPresenter(mPresenter); } + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + @Test public void testLockScreenShowNotificationsFalse() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenShowNotificationsTrue() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenAllowPrivateNotificationsTrue() { - mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsTrue() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test - public void testCurrentUserPrivateNotificationsNotRedacted() { + public void testCurrentUserPrivateNotificationsRedacted() { // GIVEN current user doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test - public void testCurrentUserPrivateNotificationsRedacted() { + public void testCurrentUserPrivateNotificationsNotRedacted() { // GIVEN current user allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test + public void testCurrentUserPrivateNotificationsRedactedChannel() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // but doesn't allow it at the channel level + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_PRIVATE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + // THEN the notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test public void testWorkPrivateNotificationsRedacted() { // GIVEN work profile doesn't private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -244,9 +304,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted() { // GIVEN work profile allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -256,13 +316,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { // GIVEN work profile allows private notifications to show but the other users don't - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification doesn't need to be redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -275,13 +337,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkProfileRedacted_otherUsersNotRedacted() { // GIVEN work profile doesn't allow private notifications to show but the other users do - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification needs to be redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -295,11 +359,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testSecondaryUserNotRedacted_currentUserRedacted() { // GIVEN secondary profile allows private notifications to show but the current user // doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the secondary profile notification still needs to be redacted because the current // user's setting takes precedence @@ -323,6 +388,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testUpdateIsPublicMode() { when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(false); NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); mLockscreenUserManager.addNotificationStateChangedListener(listener); @@ -330,24 +396,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // first call explicitly sets user 0 to not public; notifies mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); // Calling again with keyguard now showing makes user 0 public; notifies when(mKeyguardStateController.isShowing()).thenReturn(true); mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); } @@ -356,14 +426,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testDevicePolicyDoesNotAllowNotifications() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifs on lockscreen when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -371,19 +441,18 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testDevicePolicyDoesNotAllowNotifications_userAll() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -392,98 +461,105 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @Ignore("b/286230167") public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); - // TODO (b/286230167): enable assertion verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 4 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications_userAll() { + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + NotificationEntry notifEntry = new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build(); + notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() - .setNotification(new Notification()) - .setUser(UserHandle.ALL) - .build())); + assertTrue(mLockscreenUserManager.needsRedaction(notifEntry)); } @Test public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 5 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testHideNotifications_primary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -491,16 +567,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testHideNotifications_workProfile() { - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id)); } @@ -508,67 +585,62 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } @Test public void testShowNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not - when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); - assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { - // User allows notifications - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not + // KeyguardManager does not allow notifications when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -576,31 +648,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testUserAllowsNotificationsInPublic_settingsChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // User disables mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testUserAllowsNotificationsInPublic_devicePolicyChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // DevicePolicy disables notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -608,6 +679,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } + @Test + public void testNewUserAdded() { + int newUserId = 14; + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver broadcastReceiver = + mLockscreenUserManager.getBaseBroadcastReceiverForTest(); + final Bundle extras = new Bundle(); + final Intent intent = new Intent(Intent.ACTION_USER_ADDED); + intent.putExtras(extras); + intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); + broadcastReceiver.onReceive(mContext, intent); + + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId)); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId)); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId)); + } + private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl { public TestNotificationLockscreenUserManager(Context context) { @@ -623,12 +716,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { (() -> mOverviewProxyService), NotificationLockscreenUserManagerTest.this.mKeyguardManager, mStatusBarStateController, - Handler.createAsync(Looper.myLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + mBackgroundExecutor, mDeviceProvisionedController, mKeyguardStateController, mSettings, mock(DumpManager.class), - mock(LockPatternUtils.class)); + mock(LockPatternUtils.class), + mFakeFeatureFlags); } public BroadcastReceiver getBaseBroadcastReceiverForTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 9d6ea857fefc..cfcf4257ce28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -48,9 +48,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - doCallRealMethod() - .whenever(notificationMediaManager) - .updateMediaMetaData(anyBoolean(), anyBoolean()) + doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean()) doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView } @@ -62,7 +60,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true for (metaDataChanged in listOf(true, false)) { for (allowEnterAnimation in listOf(true, false)) { - notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) + notificationMediaManager.updateMediaMetaData(metaDataChanged) verify(notificationMediaManager, never()).mediaMetadata } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java deleted file mode 100644 index aeb5b037be0c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 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.notification; - -import static com.google.common.truth.Truth.assertThat; - -import android.annotation.Nullable; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.palette.graphics.Palette; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class MediaNotificationProcessorTest extends SysuiTestCase { - - private static final int BITMAP_WIDTH = 10; - private static final int BITMAP_HEIGHT = 10; - - /** - * Color tolerance is borrowed from the AndroidX test utilities for Palette. - */ - private static final int COLOR_TOLERANCE = 8; - - @Nullable private Bitmap mArtwork; - - @After - public void tearDown() { - if (mArtwork != null) { - mArtwork.recycle(); - mArtwork = null; - } - } - - @Test - public void findBackgroundSwatch_white() { - // Given artwork that is completely white. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.WHITE); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is white - assertCloseColors(swatch.getRgb(), Color.WHITE); - } - - @Test - public void findBackgroundSwatch_red() { - // Given artwork that is completely red. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.RED); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is red - assertCloseColors(swatch.getRgb(), Color.RED); - } - - static void assertCloseColors(int expected, int actual) { - assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual)); - assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual)); - assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 83f2a5d4fbdf..b922ab39912b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.NotificationChannel; import android.content.Context; import android.os.Handler; import android.os.UserHandle; @@ -51,6 +53,9 @@ import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -96,6 +101,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; private final FakeSettings mSecureSettings = new FakeSettings(); private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @@ -116,7 +122,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mStatusBarStateController, mUserTracker, mSecureSettings, - mGlobalSettings); + mGlobalSettings, + mFeatureFlags); mKeyguardNotificationVisibilityProvider = component.getProvider(); for (CoreStartable startable : component.getCoreStartables().values()) { startable.start(); @@ -424,6 +431,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void publicMode_settingsDisallow() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -433,12 +441,59 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) .thenReturn(false); + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_settingsDisallow_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) + .thenReturn(false); + + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + // THEN filter out the entry assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test public void publicMode_notifDisallowed() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_SECRET).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_notifDisallowed_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -506,6 +561,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test + public void notificationChannelVisibilityNoOverride() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + mEntry = entryBuilder.build(); + // ranking says secret because of DPC or Setting + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_SECRET) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.) + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void notificationChannelVisibilitySecret() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + // And a VISIBILITY_SECRET NotificationChannel + NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + mEntry = entryBuilder.build(); + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) + .setChannel(channel) + .build()); + + // THEN hide the entry based on visibility. + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test public void notificationVisibilityPrivate() { // GIVEN a VISIBILITY_PRIVATE notification NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() @@ -635,7 +738,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance SysuiStatusBarStateController statusBarStateController, @BindsInstance UserTracker userTracker, @BindsInstance SecureSettings secureSettings, - @BindsInstance GlobalSettings globalSettings + @BindsInstance GlobalSettings globalSettings, + @BindsInstance FeatureFlagsClassic featureFlags ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt deleted file mode 100644 index 47c5e5b021ae..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ /dev/null @@ -1,77 +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.statusbar.notification.logging - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogcatEchoTracker -import com.android.systemui.log.core.LogLevel -import com.android.systemui.statusbar.notification.stack.StackStateLogger -import com.google.common.truth.Truth -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class StackStateLoggerTest : SysuiTestCase() { - private val logBufferCounter = LogBufferCounter() - private lateinit var logger: StackStateLogger - - @Before - fun setup() { - logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer) - } - - @Test - fun groupChildRemovalEvent() { - logger.groupChildRemovalEventProcessed(KEY) - verifyDidLog(1) - logger.groupChildRemovalAnimationEnded(KEY) - verifyDidLog(1) - } - - class LogBufferCounter { - val recentLogs = mutableListOf<Pair<String, LogLevel>>() - val tracker = - object : LogcatEchoTracker { - override val logInBackgroundThread: Boolean = false - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - recentLogs.add(tagName to level) - return true - } - } - val logBuffer = - LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false) - - fun verifyDidLog(times: Int) { - Truth.assertThat(recentLogs).hasSize(times) - recentLogs.clear() - } - } - - private fun verifyDidLog(times: Int) { - logBufferCounter.verifyDidLog(times) - } - - companion object { - private val KEY = "PACKAGE_NAME" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 8c3bfd55ecf1..f7632aa37d4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.TestUiOffloadThread +import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler +import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() + // Use main thread instead of UI offload thread to fix flakes. + mDependency.injectTestDependency( + UiOffloadThread::class.java, + TestUiOffloadThread(looper.looper) + ) + helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. + looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action2) + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() + looper.processAllMessages() + ViewUtils.attachView(root) - waitForUiOffloadThread() looper.processAllMessages() + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) - waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } + + private fun waitForActionDisabled(action: View) { + waitForCondition { + looper.processAllMessages() + !action.isEnabled + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a2be8b0e0be2..033c96ae84b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -164,6 +164,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION); + mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index ac11ff29b83a..0a7dc4e05633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -192,10 +192,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) keyguardTransitionRepository.sendTransitionStep( - TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED) + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = 1f, + transitionState = TransitionState.FINISHED + ) ) assertThat(isOnLockscreen).isFalse() + // While progressing from lockscreen, should still be true + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = 0.8f, + transitionState = TransitionState.RUNNING + ) + ) + assertThat(isOnLockscreen).isTrue() + keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 700de5305778..8344cd143c5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + 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; @@ -38,7 +40,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; -import android.view.HapticFeedbackConstants; import android.view.ViewRootImpl; import com.android.internal.logging.MetricsLogger; @@ -47,6 +48,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -122,6 +124,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private BiometricUnlockLogger mLogger; @Mock private ViewRootImpl mViewRootImpl; + @Mock + private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor; private final FakeSystemClock mSystemClock = new FakeSystemClock(); private FakeFeatureFlags mFeatureFlags; private BiometricUnlockController mBiometricUnlockController; @@ -158,7 +162,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, mSystemClock, - mFeatureFlags + mFeatureFlags, + mDeviceEntryHapticsInteractor ); biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); biometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -462,145 +467,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time just occurred - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN DO NOT vibrate the device - verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - + public void onFingerprintSuccess_requestSuccessHaptic() { // WHEN biometric fingerprint succeeds givenFingerprintModeUnlockCollapsing(); mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() { - //GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintFail_alwaysPlaysHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint fails - mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); - // THEN always vibrate the device - verify(mVibratorHelper).vibrateAuthError(anyString()); + verify(mDeviceEntryHapticsInteractor).vibrateSuccess(); } @Test - public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - + public void onFingerprintFail_requestErrorHaptic() { // WHEN biometric fingerprint fails mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); // THEN always vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.REJECT) - ); + verify(mDeviceEntryHapticsInteractor).vibrateError(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index f18af61dd314..c8cbe42fb0d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -1110,6 +1112,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // THEN no NPE when fingerprintManager is null } + @Test + public void bubbleBarVisibility() { + createCentralSurfaces(); + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + verify(mBubbles).onStatusBarVisibilityChanged(false); + + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + verify(mBubbles).onStatusBarVisibilityChanged(true); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. 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 03f5f005ee47..3556703a2fa8 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/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index e6f8c4861a94..9aafee4770de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner @@ -80,10 +82,14 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var handler: Handler + private lateinit var featureFlags: FakeFeatureFlags + @Before fun setUp() { MockitoAnnotations.initMocks(this) - + featureFlags = FakeFeatureFlags().apply { + set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) + } controller = UnlockedScreenOffAnimationController( context, wakefulnessLifecycle, @@ -95,7 +101,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController }, interactionJankMonitor, powerManager, - handler = handler + handler = handler, + featureFlags, ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) 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 812e91beee48..610fede3dfea 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 57f97ec66a00..1f8cc54211e6 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 2712b70a9745..d918fa8193bb 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 ede02d1fbc9c..1c21ebe05d84 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 8ef82c9fe7bc..ba6426586ebd 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 852ed2054fcd..889f60a08766 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 9148c7580296..18ba6c4f5d9f 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 de2b6a850e03..5f4d7bf6f371 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 218fd33ac0fc..3936bedc1059 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 187832908e2b..1d5487f31e55 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 796d5ec3dd42..c831e62dd709 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 eb6f2f81fde4..f3e334ed8a22 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 e44ff8e21270..28d632d9fcea 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 b4039d906810..028a58c6e1b0 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/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index c2f56654e00a..31263627213d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -201,11 +201,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isTrue() @@ -229,11 +229,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isFalse() @@ -526,13 +526,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) whenever(this.subscriptionId).thenReturn(567) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -546,11 +547,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) whenever(wifiManager.maxSignalLevel).thenReturn(5) getCallback().onWifiEntriesChanged() @@ -566,12 +568,13 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() @@ -628,11 +631,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) getCallback().onWifiEntriesChanged() assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) @@ -717,12 +721,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -730,6 +736,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null) getCallback().onWifiEntriesChanged() // THEN we update to no network @@ -767,6 +774,56 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test + fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + } + + @Test + fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(false) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + } + + @Test fun secondaryNetworks_activeEntriesEmpty_isEmpty() = testScope.runTest { featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true) 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 a27f8990dec1..d75a45247cbf 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/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index e76163575738..a1da16737aa4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -75,6 +75,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; + private FakeExecutor mBackgroundExecutor; + @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); @@ -91,6 +93,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); + mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); BluetoothRepository bluetoothRepository = new FakeBluetoothRepository(mMockBluetoothManager); @@ -101,6 +104,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mMockDumpManager, mock(BluetoothLogger.class), bluetoothRepository, + mBackgroundExecutor, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); @@ -205,6 +209,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onAclConnectionStateChanged(device, BluetoothProfile.STATE_CONNECTED); mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET); + mBackgroundExecutor.runAllReady(); assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); @@ -290,6 +295,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -300,6 +306,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -310,6 +317,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -320,6 +328,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -337,6 +346,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device2); mBluetoothControllerImpl.onDeviceAdded(device3); + mBackgroundExecutor.runAllReady(); + assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -349,6 +360,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device1); mBluetoothControllerImpl.onDeviceAdded(device2); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index 1250228e2d37..d33806e131d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import com.android.systemui.flags.FakeFeatureFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -77,11 +78,13 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { private lateinit var view: FrameLayout private lateinit var testableLooper: TestableLooper private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController + private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + featureFlags = FakeFeatureFlags() view = LayoutInflater.from(context) .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout @@ -98,6 +101,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { dozeParameters, screenOffAnimationController, userSwitchDialogController, + featureFlags, uiEventLogger) ViewUtils.attachView(view) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index f7e0120d3843..6ef812be0350 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,6 +15,8 @@ */ package com.android.systemui; +import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -25,6 +27,7 @@ import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.os.ParcelFileDescriptor; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; import android.testing.LeakCheck; import android.testing.TestWithLooperRule; @@ -68,6 +71,9 @@ public abstract class SysuiTestCase { new AndroidXAnimatorIsolationRule(); @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule public SysuiTestableContext mContext = new SysuiTestableContext( InstrumentationRegistry.getContext(), getLeakCheck()); @Rule @@ -88,6 +94,10 @@ public abstract class SysuiTestCase { if (isRobolectricTest()) { mContext = mContext.createDefaultDisplayContext(); } + // Set the value of a single gantry flag inside the com.android.systemui package to + // ensure all flags in that package are faked (and thus require a value to be set). + mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG); + mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext); mFakeBroadcastDispatcher = new FakeBroadcastDispatcher( mContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java new file mode 100644 index 000000000000..fdd26ebeab70 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java @@ -0,0 +1,59 @@ +/* + * 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; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +/** + * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. + */ +public class TestUiOffloadThread extends UiOffloadThread { + private final Handler mTestHandler; + + public TestUiOffloadThread(Looper looper) { + mTestHandler = new Handler(looper); + } + + @Override + public Future<?> execute(Runnable runnable) { + Looper myLooper = Looper.myLooper(); + if (myLooper != null && myLooper.isCurrentThread()) { + try { + runnable.run(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + final CompletableFuture<?> future = new CompletableFuture<>(); + mTestHandler.post(() -> { + try { + runnable.run(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 10b284a03a59..5dcc7423ecc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -47,7 +47,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor } override fun getDimensionPixelSize(id: Int): Int { - throw IllegalStateException("Don't use for tests") + return 0 } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 1a8c5830e453..30132f7747b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeCommunalWidgetRepository : CommunalWidgetRepository { private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null) override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo + override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList() fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) { _stopwatchAppWidgetInfo.value = appWidgetInfo diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index e91e9559fa1e..852611230623 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -34,7 +34,7 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { get() = _isFingerprintAuthCurrentlyAllowed private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false) - override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> + override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> get() = _isFaceAuthEnrolledAndEnabled private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt index 272d686e974b..272d686e974b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java index 197873f15d0d..288dcfe2825d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -186,7 +186,7 @@ public class FakeSensorManager extends SensorManager { } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { return false; } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 21d09792f1c7..b403a7fe8f12 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -404,5 +404,9 @@ message SystemMessage { // Notify the user that audio was lowered based on Calculated Sound Dose (CSD) NOTE_CSD_LOWER_AUDIO = 1007; + + // Notify the user about external display events related to screenshot. + // Package: com.android.systemui + NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008; } } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 10ac2ebc9b2f..f09cb19d0c1d 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/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index cd83f8f4d9d8..5af80da894cc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -68,7 +68,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo * * @see #setUserAndEnabledFeatures(int, int) */ - static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001; + static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP = 0x00000001; /** * Flag for enabling the touch exploration feature. @@ -100,7 +100,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo /** * Flag for enabling the feature to control the screen magnifier. If - * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored + * {@link #FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP} is set this flag is ignored * as the screen magnifier feature performs a super set of the work * performed by this feature. * @@ -149,7 +149,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION - | FLAG_FEATURE_SCREEN_MAGNIFIER + | FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER | FLAG_SERVICE_HANDLES_DOUBLE_TAP | FLAG_REQUEST_MULTI_FINGER_GESTURES @@ -530,7 +530,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final MagnificationGestureHandler magnificationGestureHandler = createMagnificationGestureHandler(displayId, @@ -648,7 +648,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private MagnificationGestureHandler createMagnificationGestureHandler( int displayId, Context displayContext) { final boolean detectControlGestures = (mEnabledFeatures - & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; + & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0; final boolean triggerable = (mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; MagnificationGestureHandler magnificationGestureHandler; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index aa6d800510e1..d28d291537aa 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; @@ -2597,8 +2693,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityInputFilter inputFilter = null; synchronized (mLock) { int flags = 0; - if (userState.isDisplayMagnificationEnabledLocked()) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) { + flags |= AccessibilityInputFilter + .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP; } if (userState.isShortcutMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; @@ -2890,9 +2987,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); @@ -2937,12 +3048,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) { - final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( + final boolean magnificationSingleFingerTripleTapEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userState.mUserId) == 1; - if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) { - userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled); + if ((magnificationSingleFingerTripleTapEnabled + != userState.isMagnificationSingleFingerTripleTapEnabledLocked())) { + userState.setMagnificationSingleFingerTripleTapEnabledLocked( + magnificationSingleFingerTripleTapEnabled); return true; } return false; @@ -3182,7 +3295,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // We would skip overlay display because it uses overlay window to simulate secondary // displays in one display. It's not a real display and there's no input events for it. final ArrayList<Display> displays = getValidDisplayList(); - if (userState.isDisplayMagnificationEnabledLocked() + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) { for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); @@ -3211,7 +3324,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } final boolean connect = (userState.isShortcutMagnificationEnabledLocked() - || userState.isDisplayMagnificationEnabledLocked()) + || userState.isMagnificationSingleFingerTripleTapEnabledLocked()) && (userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) || userHasMagnificationServicesLocked(userState); @@ -4896,7 +5009,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateWindowMagnificationConnectionIfNeeded(userState); // Remove magnification button UI when the magnification capability is not all mode or // magnification is disabled. - if (!(userState.isDisplayMagnificationEnabledLocked() + if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) || userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 693526adc8d6..b4efec1a4b38 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -109,7 +109,7 @@ class AccessibilityUserState { private boolean mBindInstantServiceAllowed; private boolean mIsAudioDescriptionByDefaultRequested; private boolean mIsAutoclickEnabled; - private boolean mIsDisplayMagnificationEnabled; + private boolean mIsMagnificationSingleFingerTripleTapEnabled; private boolean mIsFilterKeyEventsEnabled; private boolean mIsPerformGesturesEnabled; private boolean mAccessibilityFocusOnlyInActiveWindow; @@ -211,7 +211,7 @@ class AccessibilityUserState { mRequestMultiFingerGestures = false; mRequestTwoFingerPassthrough = false; mSendMotionEventsEnabled = false; - mIsDisplayMagnificationEnabled = false; + mIsMagnificationSingleFingerTripleTapEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; @@ -520,7 +520,7 @@ class AccessibilityUserState { .append(String.valueOf(mRequestTwoFingerPassthrough)); pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( - mIsDisplayMagnificationEnabled)); + mIsMagnificationSingleFingerTripleTapEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout)); pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout)); @@ -625,12 +625,12 @@ class AccessibilityUserState { mIsAutoclickEnabled = enabled; } - public boolean isDisplayMagnificationEnabledLocked() { - return mIsDisplayMagnificationEnabled; + public boolean isMagnificationSingleFingerTripleTapEnabledLocked() { + return mIsMagnificationSingleFingerTripleTapEnabled; } - public void setDisplayMagnificationEnabledLocked(boolean enabled) { - mIsDisplayMagnificationEnabled = enabled; + public void setMagnificationSingleFingerTripleTapEnabledLocked(boolean enabled) { + mIsMagnificationSingleFingerTripleTapEnabled = enabled; } public boolean isFilterKeyEventsEnabledLocked() { 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 c4184854e690..fc8d4f89e6a7 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 72242d265e79..e4f1d3acce6d 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 8a2aa616f8e6..4298c079a63e 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 @@ -632,6 +632,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType, @VirtualDeviceParams.DevicePolicy int devicePolicy) { super.setDevicePolicy_enforcePermission(); + if (!Flags.dynamicPolicy()) { + return; + } + switch (policyType) { case POLICY_TYPE_RECENTS: synchronized (mVirtualDeviceLock) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index f59417046c85..65975e44ee2a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -20,11 +20,15 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import static android.service.contentcapture.ContentCaptureService.setClientState; import static android.view.contentcapture.ContentCaptureHelper.toList; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; 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; @@ -112,6 +116,8 @@ 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; import java.util.Objects; @@ -138,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; @@ -203,6 +212,17 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") int mDevCfgContentProtectionBufferSize; + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionRequiredGroups; + + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionOptionalGroups; + + @GuardedBy("mLock") + int mDevCfgContentProtectionOptionalGroupsThreshold; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -226,6 +246,11 @@ public class ContentCaptureManagerService extends com.android.internal.R.string.config_defaultContentCaptureService), UserManager.DISALLOW_CONTENT_CAPTURE, /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH); + + mDevCfgContentProtectionRequiredGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS; + mDevCfgContentProtectionOptionalGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties)); @@ -422,6 +447,9 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: setFineTuneParamsFromDeviceConfig(); return; default: @@ -433,6 +461,8 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void setFineTuneParamsFromDeviceConfig() { + String contentProtectionRequiredGroupsConfig; + String contentProtectionOptionalGroupsConfig; synchronized (mLock) { mDevCfgMaxBufferSize = DeviceConfig.getInt( @@ -486,6 +516,24 @@ public class ContentCaptureManagerService extends ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); + contentProtectionRequiredGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG); + contentProtectionOptionalGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG); + mDevCfgContentProtectionOptionalGroupsThreshold = + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); if (verbose) { Slog.v( TAG, @@ -507,9 +555,24 @@ public class ContentCaptureManagerService extends + ", contentProtectionAppsBlocklistSize=" + mDevCfgContentProtectionAppsBlocklistSize + ", contentProtectionBufferSize=" - + mDevCfgContentProtectionBufferSize); + + mDevCfgContentProtectionBufferSize + + ", contentProtectionRequiredGroupsConfig=" + + contentProtectionRequiredGroupsConfig + + ", contentProtectionOptionalGroupsConfig=" + + contentProtectionOptionalGroupsConfig + + ", contentProtectionOptionalGroupsThreshold=" + + mDevCfgContentProtectionOptionalGroupsThreshold); } } + + List<List<String>> contentProtectionRequiredGroups = + parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig); + List<List<String>> contentProtectionOptionalGroups = + parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig); + synchronized (mLock) { + mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups; + mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups; + } } private void setLoggingLevelFromDeviceConfig() { @@ -786,6 +849,15 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionBufferSize: "); pw.println(mDevCfgContentProtectionBufferSize); + pw.print(prefix2); + pw.print("contentProtectionRequiredGroupsSize: "); + pw.println(mDevCfgContentProtectionRequiredGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsSize: "); + pw.println(mDevCfgContentProtectionOptionalGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsThreshold: "); + pw.println(mDevCfgContentProtectionOptionalGroupsThreshold); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -890,6 +962,42 @@ public class ContentCaptureManagerService extends return mContentCaptureManagerServiceStub; } + /** + * 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); + } + 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 { @Override @@ -1277,7 +1385,10 @@ public class ContentCaptureManagerService extends isContentCaptureReceiverEnabled || whitelistedComponents != null, new ContentCaptureOptions.ContentProtectionOptions( isContentProtectionReceiverEnabled, - mDevCfgContentProtectionBufferSize), + mDevCfgContentProtectionBufferSize, + mDevCfgContentProtectionRequiredGroups, + mDevCfgContentProtectionOptionalGroups, + mDevCfgContentProtectionOptionalGroupsThreshold), whitelistedComponents); if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options); return options; @@ -1326,6 +1437,10 @@ public class ContentCaptureManagerService extends if (!mDevCfgEnableContentProtectionReceiver) { return false; } + if (mDevCfgContentProtectionRequiredGroups.isEmpty() + && mDevCfgContentProtectionOptionalGroups.isEmpty()) { + return false; + } } return mContentProtectionConsentManager.isConsentGranted(userId) && mContentProtectionBlocklistManager.isAllowed(packageName); diff --git a/services/core/Android.bp b/services/core/Android.bp index e5225f65f22a..1d02e4c88b74 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -191,7 +191,7 @@ java_library_static { "ImmutabilityAnnotation", "securebox", "apache-commons-math", - "power_optimization_flags_lib", + "backstage_power_flags_lib", "notification_flags_lib", "camera_platform_flags_core_java_lib", "biometrics_flags_lib", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 8df54569cccd..638abdba36ec 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1387,11 +1387,12 @@ public abstract class PackageManagerInternal { @UserIdInt int userId); /** - * Tells PackageManager when a component (except BroadcastReceivers) of the package is used + * Tells PackageManager when a component of the package is used * and the package should get out of stopped state and be enabled. */ public abstract void notifyComponentUsed(@NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo); + @UserIdInt int userId, @Nullable String recentCallingPackage, + @NonNull String debugInfo); /** @deprecated For legacy shell command only. */ @Deprecated diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 962f38f10b5d..beea063221fb 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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 5fb889a23fc5..1650a96a4012 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5309,7 +5309,7 @@ public class AccountManagerService if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); } - long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE; + long flags = Context.BIND_AUTO_CREATE; if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) { flags |= Context.BIND_ALLOW_INSTANT; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 553b08501925..5f1a7e7e8123 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()); } } @@ -3662,8 +3678,8 @@ public final class ActiveServices { || (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0; final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0; - final boolean filterOutQuarantined = - (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0; + final boolean matchQuarantined = + (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0; ProcessRecord attributedApp = null; if (sdkSandboxClientAppUid > 0) { @@ -3673,7 +3689,7 @@ public final class ActiveServices { isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant, null /* fgsDelegateOptions */, - inSharedIsolatedProcess, filterOutQuarantined); + inSharedIsolatedProcess, matchQuarantined); if (res == null) { return 0; } @@ -4186,7 +4202,7 @@ public final class ActiveServices { sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant, fgsDelegateOptions, inSharedIsolatedProcess, - false /* filterOutQuarantined */); + false /* matchQuarantined */); } private ServiceLookupResult retrieveServiceLocked(Intent service, @@ -4195,7 +4211,7 @@ public final class ActiveServices { String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions, - boolean inSharedIsolatedProcess, boolean filterOutQuarantined) { + boolean inSharedIsolatedProcess, boolean matchQuarantined) { if (isSdkSandboxService && instanceName == null) { throw new IllegalArgumentException("No instanceName provided for sdk sandbox process"); } @@ -4317,8 +4333,8 @@ public final class ActiveServices { if (allowInstant) { flags |= PackageManager.MATCH_INSTANT; } - if (filterOutQuarantined) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; + if (matchQuarantined) { + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; } // TODO: come back and remove this assumption to triage all services ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service, @@ -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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a97f005f4ebe..31817f1c427d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -58,7 +58,6 @@ import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTAT import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_ANY_USER; @@ -1532,6 +1531,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ int mBootPhase; + /** + * The time stamp that all apps have received BOOT_COMPLETED. + */ + volatile long mBootCompletedTimestamp; + @GuardedBy("this") boolean mDeterministicUidIdle = false; @@ -5164,10 +5168,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - synchronized (mProcLock) { - mAppProfiler.requestPssAllProcsLPr( - SystemClock.uptimeMillis(), true, false); - } + mBootCompletedTimestamp = SystemClock.uptimeMillis(); + // Defer the full Pss collection as the system is really busy now. + mHandler.postDelayed(() -> { + synchronized (mProcLock) { + mAppProfiler.requestPssAllProcsLPr( + SystemClock.uptimeMillis(), true, false); + } + }, mConstants.FULL_PSS_MIN_INTERVAL); } }); maybeLogUserspaceRebootEvent(); @@ -14286,8 +14294,7 @@ public class ActivityManagerService extends IActivityManager.Stub private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType, int callingUid, int[] users, int[] broadcastAllowList) { // TODO: come back and remove this assumption to triage all broadcasts - long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING - | FILTER_OUT_QUARANTINED_COMPONENTS; + long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING; List<ResolveInfo> receivers = null; HashSet<ComponentName> singleUserReceivers = null; diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java index 9ba49ce35dad..3e17930e3cb9 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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2249607863d5..0ab81a5e4eac 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -91,6 +91,7 @@ import android.telephony.ModemActivityInfo; import android.telephony.NetworkRegistrationInfo; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; +import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.StatsEvent; @@ -99,8 +100,10 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BinderCallsStats; +import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.CpuScalingPolicyReader; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.internal.os.RailStats; import com.android.internal.os.RpmStats; @@ -114,16 +117,21 @@ import com.android.server.Watchdog; import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; import com.android.server.power.optimization.Flags; +import com.android.server.power.stats.AggregatedPowerStatsConfig; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; -import com.android.server.power.stats.BatteryUsageStatsStore; +import com.android.server.power.stats.PowerStatsAggregator; +import com.android.server.power.stats.PowerStatsScheduler; +import com.android.server.power.stats.PowerStatsStore; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.wakeups.CpuWakeupStats; import java.io.File; import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -136,6 +144,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -153,20 +162,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub static final String TAG = "BatteryStatsService"; static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason"; static final boolean DBG = false; - private static final boolean BATTERY_USAGE_STORE_ENABLED = true; private static IBatteryStats sService; private final PowerProfile mPowerProfile; private final CpuScalingPolicies mCpuScalingPolicies; + private final MonotonicClock mMonotonicClock; private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig; final BatteryStatsImpl mStats; final CpuWakeupStats mCpuWakeupStats; - private final BatteryUsageStatsStore mBatteryUsageStatsStore; + private final PowerStatsStore mPowerStatsStore; + private final PowerStatsAggregator mPowerStatsAggregator; + private final PowerStatsScheduler mPowerStatsScheduler; private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider; private final Context mContext; private final BatteryExternalStatsWorker mWorker; private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private final AtomicFile mConfigFile; + private volatile boolean mMonitorEnabled = true; private native void getRailEnergyPowerStats(RailStats railStats); @@ -376,6 +389,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml")); mPowerProfile = new PowerProfile(context); mCpuScalingPolicies = new CpuScalingPolicyReader().read(); @@ -391,23 +405,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu) .build(); - mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this, - this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies); + mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, + systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile, + mCpuScalingPolicies); mWorker = new BatteryExternalStatsWorker(context, mStats); mStats.setExternalStatsSyncLocked(mWorker); mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); mStats.startTrackingSystemServerCpuTime(); - if (BATTERY_USAGE_STORE_ENABLED) { - mBatteryUsageStatsStore = - new BatteryUsageStatsStore(context, mStats, systemDir, mHandler); - } else { - mBatteryUsageStatsStore = null; - } + AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig(); + mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig); mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, - mBatteryUsageStatsStore); + mPowerStatsStore); + mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, + mStats.getHistory()); + final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); + final long powerStatsAggregationPeriod = context.getResources().getInteger( + com.android.internal.R.integer.config_powerStatsAggregationPeriod); + mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, + aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, + Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider); mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); + mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); + } + + private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() { + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + return config; } /** @@ -466,9 +500,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub */ public void onSystemReady() { mStats.onSystemReady(); - if (BATTERY_USAGE_STORE_ENABLED) { - mBatteryUsageStatsStore.onSystemReady(); - } + mPowerStatsScheduler.start(Flags.streamlinedBatteryStats()); } private final class LocalService extends BatteryStatsInternal { @@ -639,6 +671,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub // Shutdown the thread we made. mWorker.shutdown(); + + // To insure continuity, write the monotonic timeshift after writing the last history event + mMonotonicClock.write(); } public static IBatteryStats getService() { @@ -892,12 +927,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0); break; case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: - if (!BATTERY_USAGE_STORE_ENABLED) { - return StatsManager.PULL_SKIP; - } - - final long sessionStart = mBatteryUsageStatsStore - .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); + final long sessionStart = + getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); final long sessionEnd; synchronized (mStats) { sessionEnd = mStats.getStartClockTime(); @@ -910,8 +941,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .aggregateSnapshots(sessionStart, sessionEnd) .build(); bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0); - mBatteryUsageStatsStore - .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd); + setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd); break; default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -2641,7 +2671,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub } private void dumpAggregatedStats(PrintWriter pw) { - mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0); + mPowerStatsScheduler.aggregateAndDumpPowerStats(pw); + } + + private void dumpPowerStatsStore(PrintWriter pw) { + mPowerStatsStore.dump(new IndentingPrintWriter(pw, " ")); + } + + private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) { + mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, " ")); } private void dumpMeasuredEnergyStats(PrintWriter pw) { @@ -2789,7 +2827,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.resetAllStatsAndHistoryLocked( BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - mBatteryUsageStatsStore.removeAllSnapshots(); + mPowerStatsStore.reset(); pw.println("Battery stats and history reset."); noOutput = true; } @@ -2891,6 +2929,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub } else if ("--aggregated".equals(arg)) { dumpAggregatedStats(pw); return; + } else if ("--store".equals(arg)) { + dumpPowerStatsStore(pw); + return; + } else if ("--store-toc".equals(arg)) { + dumpPowerStatsStoreTableOfContents(pw); + return; } else if ("-a".equals(arg)) { flags |= BatteryStats.DUMP_VERBOSE; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ @@ -2951,7 +2995,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub in.unmarshall(raw, 0, raw.length); in.setDataPosition(0); BatteryStatsImpl checkinStats = new BatteryStatsImpl( - mBatteryStatsConfig, + mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies); @@ -2993,7 +3037,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub in.unmarshall(raw, 0, raw.length); in.setDataPosition(0); BatteryStatsImpl checkinStats = new BatteryStatsImpl( - mBatteryStatsConfig, + mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies); @@ -3360,6 +3404,52 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = + "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; + + /** + * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull + * in persistent file. + */ + public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { + synchronized (mConfigFile) { + Properties props = new Properties(); + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, + String.valueOf(timestamp)); + FileOutputStream out = null; + try { + out = mConfigFile.startWrite(); + props.store(out, "Statsd atom pull timestamps"); + mConfigFile.finishWrite(out); + } catch (IOException e) { + mConfigFile.failWrite(out); + Slog.e(TAG, "Cannot save config file " + mConfigFile, e); + } + } + } + + /** + * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET + * statsd atom pull. + */ + public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { + synchronized (mConfigFile) { + Properties props = new Properties(); + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + return Long.parseLong( + props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); + } + } + /** * Sets battery AC charger to enabled/disabled, and freezes the battery state. */ diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 127c5b389d79..3c56752d08e3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1440,10 +1440,9 @@ public class BroadcastQueueImpl extends BroadcastQueue { r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED); } - // Broadcast is being executed, its package can't be stopped. try { - mService.mPackageManagerInt.setPackageStoppedState( - r.curComponent.getPackageName(), false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString()); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.curComponent.getPackageName() + ": " + e); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a42890707368..b48169788180 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) { @@ -1977,8 +1982,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.notifyPackageUse(receiverPackageName, PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); - mService.mPackageManagerInt.setPackageStoppedState( - receiverPackageName, false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + receiverPackageName, r.userId, r.callerPackage, r.toString()); } private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec..e0e6cade5f27 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1439,7 +1439,7 @@ public final class ProcessList { } public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, - boolean sleeping, long now) { + boolean sleeping, long now, long earliest) { boolean first; float scalingFactor; final int memState = sProcStateToProcMem[procState]; @@ -1470,7 +1470,7 @@ public final class ProcessList { if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } - return now + delay; + return Math.max(now + delay, earliest); } long getMemLevel(int adjustment) { diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index c1f86e0c9bb4..940c58b7a5f0 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -575,7 +575,11 @@ final class ProcessProfileRecord { @GuardedBy("mProfilerLock") long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) { - return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now); + return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now, + // Cap the Pss time to make sure no Pss is collected during the very few + // minutes after the system is boot, given the system is already busy. + Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime) + + mService.mConstants.FULL_PSS_MIN_INTERVAL); } private static void commitNextPssTime(ProcStateMemTracker tracker) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 3771c05a294e..f02b8c737f90 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -511,8 +511,8 @@ class ProcessRecord implements WindowProcessListener { pw.print(prefix); pw.print("pid="); pw.println(mPid); pw.print(prefix); pw.print("lastActivityTime="); TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw); - pw.print(prefix); pw.print("startUptimeTime="); - TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw); + pw.print(prefix); pw.print("startUpTime="); + TimeUtils.formatDuration(mStartUptime, nowUptime, pw); pw.print(prefix); pw.print("startElapsedTime="); TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw); pw.println(); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71b..2d231b3cf42e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -152,6 +152,7 @@ public class SettingsToPropertiesMapper { "preload_safety", "responsible_apis", "rust", + "safety_center", "system_performance", "test_suites", "text", @@ -159,13 +160,23 @@ public class SettingsToPropertiesMapper { "tv_system_ui", "vibrator", "virtual_devices", + "wear_calling_messaging", + "wear_connectivity", + "wear_esim_carriers", "wear_frameworks", + "wear_health_services", + "wear_media", + "wear_offload", + "wear_security", "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 +272,22 @@ public class SettingsToPropertiesMapper { } }); } + + // add sys prop sync callback for staged flag values + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_REBOOT_STAGING, + AsyncTask.THREAD_POOL_EXECUTOR, + (DeviceConfig.Properties properties) -> { + String scope = properties.getNamespace(); + for (String key : properties.getKeyset()) { + String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key); + if (aconfigPropertyName == null) { + log("unable to construct system property for " + scope + "/" + key); + return; + } + setProperty(aconfigPropertyName, properties.getString(key, null)); + } + }); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -332,6 +359,35 @@ public class SettingsToPropertiesMapper { } /** + * system property name constructing rule for staged aconfig flags, the flag name + * is in the form of [namespace]*[actual flag name], we should push the following + * to system properties + * "next_boot.[actual sys prop name]". + * If the name contains invalid characters or substrings for system property name, + * will return null. + * @param flagName + * @return + */ + @VisibleForTesting + static String makeAconfigFlagStagedPropertyName(String flagName) { + int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); + if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { + log("invalid staged flag: " + flagName); + return null; + } + + String propertyName = "next_boot." + makeAconfigFlagPropertyName( + flagName.substring(0, idx), flagName.substring(idx+1)); + + if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) + || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { + return null; + } + + return propertyName; + } + + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". * If the name contains invalid characters or substrings for system property name, diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 26d99d843c7e..bb9ea285385b 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.am" flag { name: "oomadjuster_correctness_rewrite" - namespace: "android_platform_power_optimization" + namespace: "backstage_power" description: "Utilize new OomAdjuster implementation" bug: "298055811" is_fixed_read_only: true diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 9cfac9af3991..eea3d3885b34 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -68,6 +68,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -496,8 +497,9 @@ public class AudioDeviceBroker { AudioDeviceInfo.TYPE_AUX_LINE }; - /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { - return isValidCommunicationDeviceType(device.getType()); + /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device, "device must not be null"); + return device.isSink() && isValidCommunicationDeviceType(device.getType()); } private static boolean isValidCommunicationDeviceType(int deviceType) { diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index 6c0fef5f628d..5f4e4c3bc4e0 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -157,7 +157,8 @@ public class MusicFxHelper { Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE"); // Once the uid is no longer running, close all remain audio session(s) for this UID if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) { - final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid)); + final List<Integer> sessions = + new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid))); Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions"); for (Integer session : sessions) { Intent intent = new Intent( diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 6c5f3e74b0d2..d65c7c2c526d 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 4538cad513d6..1760bb3c7a6a 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; @@ -357,6 +358,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 { checkInternalPermission(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462..9569f23e8d49 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() { @@ -705,6 +745,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, IInvalidationCallback callback) { @@ -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 b537e0eae548..b12d831ffe24 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -6,3 +6,10 @@ flag { description: "This flag controls tunscany virtual HAL feature" bug: "294254230" } + +flag { + name: "de_hidl" + namespace: "biometrics_framework" + description: "feature flag for biometrics de-hidl" + bug: "287332354" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 78c95ad4576b..a47135fd032f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -35,6 +35,7 @@ import android.util.EventLog; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -116,7 +117,24 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> @LockoutTracker.LockoutMode public int handleFailedAttempt(int userId) { - return LockoutTracker.LOCKOUT_NONE; + if (Flags.deHidl()) { + if (mLockoutTracker != null) { + mLockoutTracker.addFailedAttemptForUser(getTargetUserId()); + } + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(userId); + final PerformanceTracker performanceTracker = + PerformanceTracker.getInstanceForSensorId(getSensorId()); + if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) { + performanceTracker.incrementPermanentLockoutForUser(userId); + } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) { + performanceTracker.incrementTimedLockoutForUser(userId); + } + + return lockoutMode; + } else { + return LockoutTracker.LOCKOUT_NONE; + } } protected long getStartTimeMs() { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 8a54ae5a6fea..037ea38a2d17 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -586,4 +586,13 @@ public class BiometricScheduler { } }, 10000); } + + /** + * Handle stop user client when user switching occurs. + */ + public void onUserStopped() {} + + public Handler getHandler() { + return mHandler; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java index 95c49032c029..35e9bcbbf085 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java @@ -32,6 +32,7 @@ public class LockoutCache implements LockoutTracker { mUserLockoutStates = new SparseIntArray(); } + @Override public void setLockoutModeForUser(int userId, @LockoutMode int mode) { Slog.d(TAG, "Lockout for user: " + userId + " is " + mode); synchronized (this) { diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java index 4a59c9df9bef..8271380010e2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java @@ -35,4 +35,7 @@ public interface LockoutTracker { @interface LockoutMode {} @LockoutMode int getLockoutModeForUser(int userId); + void setLockoutModeForUser(int userId, @LockoutMode int mode); + default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {} + default void addFailedAttemptForUser(int userId) {} } diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 694dfd28d0cc..80754702415a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -165,6 +165,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } } + @Override public void onUserStopped() { if (mStopUserClient == null) { Slog.e(getTag(), "Unexpected onUserStopped"); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java index cc0022703745..e5d4a635876d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java @@ -31,6 +31,11 @@ public class LockoutHalImpl implements LockoutTracker { return mCurrentUserLockoutMode; } + @Override + public void setLockoutModeForUser(int userId, @LockoutMode int mode) { + setCurrentUserLockoutMode(mode); + } + public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) { mCurrentUserLockoutMode = lockoutMode; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java index 573c20fe041b..d149f5215a7a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java @@ -38,7 +38,7 @@ import android.util.Slog; /** * Utilities for converting from hardware to framework-defined AIDL models. */ -final class AidlConversionUtils { +public final class AidlConversionUtils { private static final String TAG = "AidlConversionUtils"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..57f5b34c197a --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.face.ISessionCallback; +import android.hardware.face.Face; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.face.FaceUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutTracker mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutTracker lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onAuthenticationFrame(AuthenticationFrame frame) { + handleResponse(FaceAuthenticationClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face authentication client."); + return; + } + c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); + }, null); + } + + @Override + public void onEnrollmentFrame(EnrollmentFrame frame) { + handleResponse(FaceEnrollClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face enroll client."); + return; + } + c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FaceUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Face face = new Face(name, enrollmentId, mSensorId); + + handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Face face = new Face("" /* name */, enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + true /* authenticated */, byteList), null); + } + + @Override + public void onAuthenticationFailed() { + final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + false /* authenticated */, null /* hat */), null); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared, + (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + Utils.getCurrentStrength(mSensorId), -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; ++i) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult( + null /* identifier */, 0 /* remaining */), null); + } + } + + @Override + public void onFeaturesRetrieved(byte[] features) { + handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */, + features), null); + } + + @Override + public void onFeatureSet(byte feature) { + handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null); + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(RemovalConsumer.class, + (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1), + null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */, + 0 /* remaining */), null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved( + authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + /** + * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL). + */ + public void onLockoutChanged(long duration) { + mScheduler.getHandler().post(() -> { + @LockoutTracker.LockoutMode final int lockoutMode; + if (duration == 0) { + lockoutMode = LockoutTracker.LOCKOUT_NONE; + } else if (duration == -1 || duration == Long.MAX_VALUE) { + lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; + } else { + lockoutMode = LockoutTracker.LOCKOUT_TIMED; + } + + mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode); + + if (duration == 0) { + mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); + } + }); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> actionIfClassMatchesClient, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + actionIfClassMatchesClient.accept((T) client); + } else { + Slog.d(TAG, "Current client is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index 29eee6b5bb06..858bb864d4db 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors.face.aidl; -import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; +import android.content.Context; import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; + +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -31,14 +35,22 @@ public class AidlSession { @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -52,8 +64,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 35fc43ae5f74..470dc4b7172c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -46,8 +46,8 @@ import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.face.UsageStats; @@ -57,7 +57,8 @@ import java.util.function.Supplier; /** * Face-specific authentication client for the {@link IFace} AIDL HAL interface. */ -class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> +public class FaceAuthenticationClient + extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> implements LockoutConsumer { private static final String TAG = "FaceAuthenticationClient"; @@ -74,11 +75,11 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut @Nullable private ICancellationSignal mCancellationSignal; @Nullable - private SensorPrivacyManager mSensorPrivacyManager; + private final SensorPrivacyManager mSensorPrivacyManager; @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; - FaceAuthenticationClient(@NonNull Context context, + public FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, long operationId, @@ -86,7 +87,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication, @Authenticators.Types int sensorStrength) { this(context, lazyDaemon, token, requestId, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, @@ -103,12 +104,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, SensorPrivacyManager sensorPrivacyManager, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, token, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, - isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */, + isStrongBiometric, null /* taskStackListener */, lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); setRequestId(requestId); @@ -263,8 +264,13 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut mLastAcquire = acquireInfo; final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode); onAcquiredInternal(acquireInfo, vendorCode, shouldSend); - PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); - pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + + //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL + if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser( + getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) { + PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); + pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + } } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index f55cf0549382..dbed1f7a8f9d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -85,7 +85,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { } }; - FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index 165c3a241043..e404bd2be31e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -36,7 +36,7 @@ import java.util.function.Supplier; public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FaceGenerateChallengeClient"; - FaceGenerateChallengeClient(@NonNull Context context, + public FaceGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index ef3b345402bf..c15049b48bb2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; import android.os.IBinder; import android.os.RemoteException; import android.provider.Settings; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; import java.util.HashMap; import java.util.Map; @@ -46,14 +48,16 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen private static final String TAG = "FaceGetFeatureClient"; private final int mUserId; + private final int mFeature; - FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, int feature) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mUserId = userId; + mFeature = feature; } @Override @@ -70,7 +74,11 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen @Override protected void startHalOperation() { try { - getFreshDaemon().getSession().getFeatures(); + ISession session = getFreshDaemon().getSession(); + if (session instanceof AidlToHidlAdapter) { + ((AidlToHidlAdapter) session).setFeature(mFeature); + } + session.getFeatures(); } catch (RemoteException e) { Slog.e(TAG, "Unable to getFeature", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index f09d192966f1..e75c6aba1489 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -38,9 +38,9 @@ import java.util.function.Supplier; /** * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface. */ -class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { +public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { - FaceInternalCleanupClient(@NonNull Context context, + public FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index cc3118cc3433..dd9c6d50ae9e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -493,7 +493,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(), + mUsageStats, null /* lockoutTracker */, allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override @@ -619,7 +619,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mFaceSensors.get(sensorId).getLazySession(), token, callback, userId, mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext); + mBiometricContext, feature); scheduleForSensor(sensorId, client); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 0512017394af..079388822def 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -36,12 +36,12 @@ import java.util.function.Supplier; /** * Face-specific removal client for the {@link IFace} AIDL HAL interface. */ -class FaceRemovalClient extends RemovalClient<Face, AidlSession> { +public class FaceRemovalClient extends RemovalClient<Face, AidlSession> { private static final String TAG = "FaceRemovalClient"; final int[] mBiometricIds; - FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 1a12fcdf5010..77b5592c5064 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -48,14 +47,14 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem private static final String TAG = "FaceResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FaceResetLockoutClient(@NonNull Context context, + public FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,7 +106,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index 8838345de4d6..0d6143a7d0f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -38,7 +38,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession private final long mChallenge; - FaceRevokeChallengeClient(@NonNull Context context, + public FaceRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 6c143872ff8c..f6da8726564f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -46,7 +46,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen private final boolean mEnabled; private final HardwareAuthToken mHardwareAuthToken; - FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 2ad41c2a7a02..54e66eb4cca4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -23,16 +23,10 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.face.AuthenticationFrame; -import android.hardware.biometrics.face.EnrollmentFrame; -import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; -import android.hardware.biometrics.face.ISessionCallback; -import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -44,29 +38,22 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.face.FaceUtils; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -91,397 +78,6 @@ public class Sensor { @NonNull private final Supplier<AidlSession> mLazySession; @Nullable AidlSession mCurrentSession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceGenerateChallengeClient generateChallengeClient = - (FaceGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceRevokeChallengeClient revokeChallengeClient = - (FaceRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAuthenticationFrame(AuthenticationFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceAuthenticationClient)) { - Slog.e(mTag, "onAuthenticationFrame for incompatible client: " - + Utils.getClientName(client)); - return; - - } - if (frame == null) { - Slog.e(mTag, "Received null authentication frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceAuthenticationClient) client).onAuthenticationFrame( - AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); - }); - } - - @Override - public void onEnrollmentFrame(EnrollmentFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentFrame for incompatible client: " - + Utils.getClientName(client)); - return; - } - if (frame == null) { - Slog.e(mTag, "Received null enrollment frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceEnrollClient) client).onEnrollmentFrame( - AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FaceUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Face face = new Face(name, enrollmentId, mSensorId); - - final FaceEnrollClient enrollClient = (FaceEnrollClient) client; - enrollClient.onEnrollResult(face, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); - authenticationConsumer.onAuthenticated(face, false /* authenticated */, - null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FaceResetLockoutClient resetLockoutClient = - (FaceResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceDetectClient)) { - Slog.e(mTag, "onInteractionDetected for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceDetectClient detectClient = (FaceDetectClient) client; - detectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; ++i) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onFeaturesRetrieved(byte[] features) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetFeatureClient)) { - Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: " - + Utils.getClientName(client)); - return; - } - final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client; - faceGetFeatureClient.onFeatureGet(true /* success */, features); - }); - - } - - @Override - public void onFeatureSet(byte feature) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceSetFeatureClient)) { - Slog.e(mTag, "onFeatureSet for non-set consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client; - faceSetFeatureClient.onFeatureSet(true /* success */); - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(face, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceGetAuthenticatorIdClient getAuthenticatorIdClient = - (FaceGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @@ -511,9 +107,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..eecf44b92918 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.EnrollmentStageConfig; +import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.common.NativeHandle; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + + private final String TAG = "AidlToHidlAdapter"; + private static final int CHALLENGE_TIMEOUT_SEC = 600; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000; + private static final int INVALID_VALUE = -1; + private final Clock mClock; + private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75; + private long mGenerateChallengeCreatedAt = INVALID_VALUE; + private long mGenerateChallengeResult = INVALID_VALUE; + @NonNull private Supplier<IBiometricsFace> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + private final Context mContext; + private int mFeature = INVALID_VALUE; + + public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, + int userId, AidlResponseHandler aidlResponseHandler) { + this(context, session, userId, aidlResponseHandler, Clock.systemUTC()); + } + + AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler, Clock clock) { + mSession = session; + mUserId = userId; + mContext = context; + mClock = clock; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setCallback(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + private boolean isGeneratedChallengeCacheValid() { + return mGenerateChallengeCreatedAt != INVALID_VALUE + && mGenerateChallengeResult != INVALID_VALUE + && mClock.millis() - mGenerateChallengeCreatedAt + < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS; + } + + private void incrementChallengeCount() { + mGeneratedChallengeCount.add(0, mClock.millis()); + } + + private int decrementChallengeCount() { + final long now = mClock.millis(); + // ignore values that are old in case generate/revoke calls are not matched + // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing + mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS); + if (!mGeneratedChallengeCount.isEmpty()) { + mGeneratedChallengeCount.remove(0); + } + return mGeneratedChallengeCount.size(); + } + + @Override + public void generateChallenge() throws RemoteException { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + return; + } + mGenerateChallengeCreatedAt = mClock.millis(); + mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value; + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); + return; + } + mGenerateChallengeCreatedAt = INVALID_VALUE; + mGenerateChallengeResult = INVALID_VALUE; + mSession.get().revokeChallenge(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException { + //unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface) throws RemoteException { + final ArrayList<Byte> token = new ArrayList<>(); + final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat); + for (byte b : hardwareAuthTokenArray) { + token.add(b); + } + final ArrayList<Integer> disabledFeatures = new ArrayList<>(); + for (byte b: features) { + disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b)); + } + mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + mSession.get().remove(enrollmentIds[0]); + } + + /** + * Needs to be called before getFeatures is invoked. + */ + public void setFeature(int feature) { + mFeature = feature; + } + + @Override + public void getFeatures() throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) { + return; + } + + final OptionalBool result = mSession.get() + .getFeature(mFeature, faceId); + + if (result.status == Status.OK && result.value) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils + .convertFrameworkToAidlFeature(mFeature)}); + } else if (result.status == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{}); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + + mFeature = INVALID_VALUE; + } + + @Override + public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) + throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE) { + return; + } + ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>(); + for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthTokenList.add(b); + } + final int result = mSession.get().setFeature( + AidlConversionUtils.convertAidlToFrameworkFeature(feature), + enabled, hardwareAuthTokenList, faceId); + if (result == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureSet(feature); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + } + + private int getFaceId() { + FaceManager faceManager = mContext.getSystemService(FaceManager.class); + List<Face> faces = faceManager.getEnrolledFaces(mUserId); + if (faces.isEmpty()) { + Slog.d(TAG, "No faces to get feature from."); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */); + return INVALID_VALUE; + } + + return faces.get(0).getBiometricId(); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + long authenticatorId = mSession.get().getAuthenticatorId().value; + mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId); + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + mSession.get().resetLockout(hardwareAuthToken); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface, OperationContext context) throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + /** + * Cancellation in HIDL occurs for the entire session, instead of a specific client. + */ + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 1499317478aa..46ce0b62e6d5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -61,6 +62,7 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -78,6 +80,8 @@ import com.android.server.biometrics.sensors.face.FaceUtils; import com.android.server.biometrics.sensors.face.LockoutHalImpl; import com.android.server.biometrics.sensors.face.ServiceProvider; import com.android.server.biometrics.sensors.face.UsageStats; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.face.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -131,7 +135,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private int mCurrentUserId = UserHandle.USER_NULL; private final int mSensorId; private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + private final LockoutResetDispatcher mLockoutResetDispatcher; private FaceGenerateChallengeClient mGeneratedChallengeCache = null; + private AidlSession mSession; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override @@ -361,6 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mLockoutTracker = new LockoutHalImpl(); mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler, mScheduler, mLockoutTracker, lockoutResetDispatcher); + mLockoutResetDispatcher = lockoutResetDispatcher; mHalResultController.setCallback(() -> { mDaemon = null; mCurrentUserId = UserHandle.USER_NULL; @@ -420,6 +427,24 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } + public int getCurrentUserId() { + return mCurrentUserId; + } + + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId, + new AidlResponseHandler(mContext, mScheduler, mSensorId, + mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher, + new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + private synchronized IBiometricsFace getDaemon() { if (mTestHalEnabled) { final TestHal testHal = new TestHal(mContext, mSensorId); @@ -551,32 +576,63 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - incrementChallengeCount(); + scheduleUpdateActiveUserWithoutHandler(userId); - if (isGeneratedChallengeCacheValid()) { - Slog.d(TAG, "Current challenge is cached and will be reused"); - mGeneratedChallengeCache.reuseResult(receiver); - return; + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); + } + } + }); + } - final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, sSystemClock.millis()); - mGeneratedChallengeCache = client; - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client." - + " Expecting: " + client + ", received: " + clientMonitor); - } + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mGeneratedChallengeCache.reuseResult(receiver); + return; + } + + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, + opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, sSystemClock.millis()); + mGeneratedChallengeCache = client; + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); } - }); + } }); } @@ -584,31 +640,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final boolean shouldRevoke = decrementChallengeCount() == 0; - if (!shouldRevoke) { - Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " - + mGeneratedChallengeCount); - return; + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } + }); + } + + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient( + mContext, this::getSession, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); + } } + }); + } - Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); - mGeneratedChallengeCache = null; + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + return; + } - final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, - mLazyDaemon, token, userId, opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleRevokeChallenge, mismatched client." - + "Expecting: " + client + ", received: " + clientMonitor); - } + Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); + mGeneratedChallengeCache = null; + final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, + mLazyDaemon, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); } - }); + } }); } @@ -620,7 +707,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } + }); + return id; + } + + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { + final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, + hardwareAuthToken, opPackageName, id, + FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, + ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_faceMaxTemplatesPerUser), + false); + + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId()); + } + } + }); + } + + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, @@ -628,7 +770,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -650,8 +791,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } }); - }); - return id; } @Override @@ -683,18 +822,46 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); - final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, - mLazyDaemon, token, requestId, receiver, operationId, restricted, - options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, mLockoutTracker, - mUsageStats, allowBackgroundAuthentication, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient( + mContext, this::getSession, token, requestId, receiver, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mUsageStats, mLockoutTracker, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, + mLazyDaemon, token, requestId, receiver, operationId, restricted, options, + cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mLockoutTracker, mUsageStats, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter receiver, @@ -719,13 +886,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId); + } }); } @@ -736,17 +901,39 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFace@1.0, remove(0) means remove all enrollments - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, - opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, 0); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, 0); + } }); } + private void scheduleRemoveAidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveHidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), faceId, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { mHandler.post(() -> { @@ -756,89 +943,180 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, hardwareAuthToken); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(userId, hardwareAuthToken); + } }); } + private void scheduleResetLockoutAidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, mSensorProperties.sensorStrength); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, + mLazyDaemon, + userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); + } else { + scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); + return; + } + final int faceId = faces.get(0).getBiometricId(); + final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon, + token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, + enabled, hardwareAuthToken, faceId); + mScheduler.scheduleClientMonitor(client); + } - final int faceId = faces.get(0).getBiometricId(); - final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext, - feature, enabled, hardwareAuthToken, faceId); - mScheduler.scheduleClientMonitor(client); - }); + private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature, enabled, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); } + @Override public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + + if (Flags.deHidl()) { + scheduleGetFeatureAidl(token, userId, feature, listener, + opPackageName); + } else { + scheduleGetFeatureHidl(sensorId, token, userId, feature, listener, + opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); + return; + } - final int faceId = faces.get(0).getBiometricId(); - final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, - token, listener, userId, opPackageName, mSensorId, - BiometricLogger.ofUnknown(mContext), mBiometricContext, - feature, faceId); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished( - @NonNull BaseClientMonitor clientMonitor, boolean success) { - if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { - final int settingsValue = client.getValue() ? 1 : 0; - Slog.d(TAG, "Updating attention value for user: " + userId - + " to value: " + settingsValue); - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, - settingsValue, userId); - } + final int faceId = faces.get(0).getBiometricId(); + final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, + token, listener, userId, opPackageName, mSensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (success + && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { + final int settingsValue = client.getValue() ? 1 : 0; + Slog.d(TAG, + "Updating attention value for user: " + userId + " to value: " + + settingsValue); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue, + userId); } - }); + } }); } + private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient( + mContext, this::getSession, token, listener, userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature); + mScheduler.scheduleClientMonitor(client); + } + private void scheduleInternalCleanup(int userId, @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback, - mBiometricStateCallback)); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { @@ -970,6 +1248,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } mCurrentUserId = targetUserId; } else { Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..36a9790d2d4b --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub { + + private final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult( + long deviceId, int faceId, int userId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(faceId, remaining); + } + + @Override + public void onAuthenticated(long deviceId, int faceId, int userId, + ArrayList<Byte> token) { + final boolean authenticated = faceId != 0; + byte[] hardwareAuthToken = new byte[token.size()]; + + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + + if (authenticated) { + mAidlResponseHandler.onAuthenticationSucceeded(faceId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onAcquired(long deviceId, int userId, int acquiredInfo, + int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onError(long deviceId, int userId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) { + int[] enrollmentIds = new int[removed.size()]; + for (int i = 0; i < removed.size(); i++) { + enrollmentIds[i] = removed.get(i); + } + mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds); + } + + @Override + public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) { + int[] enrollmentIds = new int[faceIds.size()]; + for (int i = 0; i < faceIds.size(); i++) { + enrollmentIds[i] = faceIds.get(i); + } + mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds); + } + + @Override + public void onLockoutChanged(long duration) { + mAidlResponseHandler.onLockoutChanged(duration); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onFeatureGet(byte[] features) { + mAidlResponseHandler.onFeaturesRetrieved(features); + } + + void onFeatureSet(byte feature) { + mAidlResponseHandler.onFeatureSet(feature); + } + + void onAuthenticatorIdRetrieved(long authenticatorId) { + mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..4a019436cf6f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutCache mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutCache lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated( + mSensorId, mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked( + challenge), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + @Override + public void onAcquired(byte info, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired( + AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FingerprintUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Fingerprint fingerprint = new Fingerprint(name, currentUserId, + enrollmentId, mSensorId); + handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint, + remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + true /* authenticated */, byteList), (c) -> onInteractionDetected()); + } + + @Override + public void onAuthenticationFailed() { + final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + false /* authenticated */, null /* hardwareAuthToken */), + (c) -> onInteractionDetected()); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), + null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FingerprintResetLockoutClient.class, + FingerprintResetLockoutClient::onLockoutCleared, + (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone( + mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher, + mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId), + -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FingerprintDetectClient.class, + FingerprintDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null, + 0), null); + } + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0), + null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FingerprintGetAuthenticatorIdClient.class, + (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> action, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + action.accept((T) client); + } else { + Slog.e(TAG, "Client monitor is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 55861bb4cc6f..299a310caee9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -16,10 +16,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; + +import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -30,14 +33,22 @@ public class AidlSession { private final int mHalInterfaceVersion; @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session, + int userId, AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -51,8 +62,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 54d1faa39be0..337c3c299e54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -51,8 +52,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; @@ -66,7 +67,7 @@ import java.util.function.Supplier; * Fingerprint-specific authentication client supporting the {@link * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintAuthenticationClient +public class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; @@ -93,7 +94,7 @@ class FingerprintAuthenticationClient private Runnable mAuthSuccessRunnable; private final Clock mClock; - FingerprintAuthenticationClient( + public FingerprintAuthenticationClient( @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @@ -108,14 +109,14 @@ class FingerprintAuthenticationClient @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, - @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @Authenticators.Types int biometricStrength, - @NonNull Clock clock) { + @NonNull Clock clock, + @Nullable LockoutTracker lockoutTracker) { super( context, lazyDaemon, @@ -130,7 +131,7 @@ class FingerprintAuthenticationClient biometricContext, isStrongBiometric, taskStackListener, - null /* lockoutCache */, + lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); @@ -211,6 +212,7 @@ class FingerprintAuthenticationClient boolean authenticated, ArrayList<Byte> token) { super.onAuthenticated(identifier, authenticated, token); + handleLockout(authenticated); if (authenticated) { mState = STATE_STOPPED; mSensorOverlays.hide(getSensorId()); @@ -219,6 +221,32 @@ class FingerprintAuthenticationClient } } + private void handleLockout(boolean authenticated) { + if (getLockoutTracker() == null) { + Slog.d(TAG, "Lockout is implemented by the HAL"); + return; + } + if (authenticated) { + getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */, + getTargetUserId()); + } else { + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(getTargetUserId()); + if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { + Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")"); + final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED + ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + // Send the error, but do not invoke the FinishCallback yet. Since lockout is not + // controlled by the HAL, the framework must stop the sensor before finishing the + // client. + mSensorOverlays.hide(getSensorId()); + onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); + cancel(); + } + } + } + @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 4502e5d0c4b6..e2413ee1c016 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -43,7 +43,8 @@ import java.util.function.Supplier; * Performs fingerprint detection without exposing any matching information (e.g. accept/reject * have the same haptic, lockout counter is not increased). */ -class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer { +public class FingerprintDetectClient extends AcquisitionClient<AidlSession> + implements DetectionConsumer { private static final String TAG = "FingerprintDetectClient"; @@ -52,7 +53,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements @NonNull private final SensorOverlays mSensorOverlays; @Nullable private ICancellationSignal mCancellationSignal; - FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FingerprintDetectClient(@NonNull Context context, + @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, @NonNull FingerprintAuthenticateOptions options, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 46ff6b4fab1a..06550d8b4fce 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -49,14 +49,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; -import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.function.Supplier; -class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, +public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, PowerPressHandler { private static final String TAG = "FingerprintEnrollClient"; @@ -72,12 +71,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps private static boolean shouldVibrateFor(Context context, FingerprintSensorPropertiesInternal sensorProps) { - final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); - final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); - return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + if (sensorProps != null) { + final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); + final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); + return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + } else { + return true; + } } - FingerprintEnrollClient(@NonNull Context context, + public FingerprintEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @@ -89,8 +92,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger, - biometricContext); + 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), + logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); @@ -136,7 +139,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE - if (mSensorProps.isAnyUdfpsType()) { + if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) { if (acquiredGood && mShouldVibrate) { vibrateSuccess(); } @@ -162,8 +165,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @Override protected boolean hasReachedEnrollmentLimit() { - return FingerprintUtils.getInstance(getSensorId()) - .getBiometricsForUser(getContext(), getTargetUserId()).size() + return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size() >= mMaxTemplatesPerUser; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index ddae8bedf7c1..ce693ff58e19 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -33,10 +33,10 @@ import java.util.function.Supplier; /** * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { +public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FingerprintGenerateChallengeClient"; - FingerprintGenerateChallengeClient(@NonNull Context context, + public FingerprintGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index ff9127f516af..5edc2ca080ad 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -39,9 +39,10 @@ import java.util.function.Supplier; * Fingerprint-specific internal cleanup client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> { +public class FingerprintInternalCleanupClient + extends InternalCleanupClient<Fingerprint, AidlSession> { - FingerprintInternalCleanupClient(@NonNull Context context, + public FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index e42b66472cbe..9985b06833c9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -428,8 +428,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, + opPackageName, FingerprintUtils.getInstance(sensorId), + sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), @@ -496,12 +496,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(), + mTaskStackListener, mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), - SystemClock.elapsedRealtimeClock()); + SystemClock.elapsedRealtimeClock(), + null /* lockoutTracker */); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index d559bb1d72f1..4f08f6fbde54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -37,12 +37,12 @@ import java.util.function.Supplier; * Fingerprint-specific removal client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface. */ -class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { +public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { private static final String TAG = "FingerprintRemovalClient"; private final int[] mBiometricIds; - FingerprintRemovalClient(@NonNull Context context, + public FingerprintRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 7a620349075c..ec225a60d54b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -43,19 +42,20 @@ import java.util.function.Supplier; * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is * cleared. */ -class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer { +public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> + implements ErrorConsumer { private static final String TAG = "FingerprintResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FingerprintResetLockoutClient(@NonNull Context context, + public FingerprintResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,10 +107,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { + lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId); lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE); lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId); authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index afa62e2e3173..23d8e1e63f63 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -32,13 +32,13 @@ import java.util.function.Supplier; /** * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { +public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { - private static final String TAG = "FingerpirntRevokeChallengeClient"; + private static final String TAG = "FingerprintRevokeChallengeClient"; private final long mChallenge; - FingerprintRevokeChallengeClient(@NonNull Context context, + public FingerprintRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @@ -57,7 +57,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession } } - void onChallengeRevoked(int sensorId, int userId, long challenge) { + void onChallengeRevoked(long challenge) { final boolean success = challenge == mChallenge; mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 56b85ceb8e6b..893cb8f9b4fc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -23,13 +23,9 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.ISession; -import android.hardware.biometrics.fingerprint.ISessionCallback; -import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -39,34 +35,25 @@ import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AcquisitionClient; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -93,348 +80,6 @@ public class Sensor { @Nullable AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGenerateChallengeClient generateChallengeClient = - (FingerprintGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintRevokeChallengeClient revokeChallengeClient = - (FingerprintRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAcquired(byte info, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " - + Utils.getClientName(client)); - return; - } - - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info), - vendorCode); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FingerprintUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId); - - final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; - enrollClient.onEnrollResult(fingerprint, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - - authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); - authenticationConsumer - .onAuthenticated(fp, false /* authenticated */, null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FingerprintResetLockoutClient resetLockoutClient = - (FingerprintResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintDetectClient)) { - Slog.e(mTag, "onInteractionDetected for non-detect client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintDetectClient fingerprintDetectClient = - (FingerprintDetectClient) client; - fingerprintDetectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0); - } - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null, 0); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = - (FingerprintGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintInvalidationClient invalidationClient = - (FingerprintInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } - Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -466,9 +111,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..b48d232e65af --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + private final String TAG = "AidlToHidlAdapter"; + @VisibleForTesting + static final int ENROLL_TIMEOUT_SEC = 60; + @NonNull + private final Supplier<IBiometricsFingerprint> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + + public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = session; + mUserId = userId; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setNotify(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + @Override + public void generateChallenge() throws RemoteException { + long challenge = mSession.get().preEnroll(); + mHidlToAidlCallbackConverter.onChallengeGenerated(challenge); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + mSession.get().postEnroll(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException { + mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId, + ENROLL_TIMEOUT_SEC); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId, mUserId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0, mUserId); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + if (enrollmentIds.length > 1) { + mSession.get().remove(mUserId, 0); + } else { + mSession.get().remove(mUserId, enrollmentIds[0]); + } + } + + @Override + public void onPointerDown(int pointerId, int x, int y, float minor, float major) + throws RemoteException { + UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major); + } + + @Override + public void onPointerUp(int pointerId) throws RemoteException { + UdfpsHelper.onFingerUp(mSession.get()); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + mHidlToAidlCallbackConverter.onResetLockout(); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onUiReady() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onPointerDownWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerUpWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerCancelWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index a655f3601a07..8bfa560b8031 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -64,6 +65,7 @@ import com.android.server.biometrics.fingerprint.PerformanceStatsProto; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -74,6 +76,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; @@ -82,6 +85,8 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; import com.android.server.biometrics.sensors.fingerprint.Udfps; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -132,6 +137,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private final boolean mIsUdfps; private final int mSensorId; private final boolean mIsPowerbuttonFps; + private AidlSession mSession; private final class BiometricTaskStackListener extends TaskStackListener { @Override @@ -413,6 +419,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider }); } + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(this::getDaemon, + mCurrentUserId, new AidlResponseHandler(mContext, + mScheduler, mSensorId, mCurrentUserId, new LockoutCache(), + mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + @VisibleForTesting synchronized IBiometricsFingerprint getDaemon() { if (mTestHalEnabled) { @@ -518,6 +538,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } mCurrentUserId = targetUserId; } else { Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); @@ -554,47 +578,116 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler // thread. mHandler.post(() -> { - final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, - userId, mContext.getOpPackageName(), sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext, mLockoutTracker); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(sensorId, userId); + } }); } + private void scheduleResetLockoutAidl(int sensorId, int userId, + @Nullable byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, + Utils.getCurrentStrength(sensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int sensorId, int userId) { + final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, + userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, mLockoutTracker); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final FingerprintGenerateChallengeClient client = - new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); + } }); } + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final FingerprintGenerateChallengeClient client = + new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( - mContext, mLazyDaemon, token, userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } }); } + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient( + mContext, this::getSession, + token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( + mContext, mLazyDaemon, token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @@ -604,38 +697,96 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), - userId, hardwareAuthToken, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - mBiometricStateCallback.onClientStarted(clientMonitor); - } + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } + }); + return id; + } - @Override - public void onBiometricAction(int action) { - mBiometricStateCallback.onBiometricAction(action); + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, + mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } + } + }); + } - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - mBiometricStateCallback.onClientFinished(clientMonitor, success); - if (success) { - // Update authenticatorIds - scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), - true /* force */); - } + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient( + mContext, + this::getSession, token, id, + new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, null /* sensorProps */, + mUdfpsOverlayController, mSidefpsController, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), + enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } - }); + } }); - return id; } @Override @@ -653,17 +804,46 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mLazyDaemon, token, id, listener, options, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, isStrongBiometric); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleFingerDetectAidl(token, listener, options, statsClient, id, + isStrongBiometric); + } else { + scheduleFingerDetectHidl(token, listener, options, statsClient, id, + isStrongBiometric); + } }); return id; } + private void scheduleFingerDetectHidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final FingerprintDetectClient client = new FingerprintDetectClient(mContext, + mLazyDaemon, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleFingerDetectAidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient( + mContext, + this::getSession, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -674,20 +854,55 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( - mContext, mLazyDaemon, token, requestId, listener, operationId, - restricted, options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, - mTaskStackListener, mLockoutTracker, - mUdfpsOverlayController, mSidefpsController, - allowBackgroundAuthentication, mSensorProperties, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient( + mContext, this::getSession, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, mHandler, + Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( + mContext, mLazyDaemon, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, mLockoutTracker, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, + Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -719,17 +934,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, - userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName); + } }); } + private void scheduleRemoveHidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, + userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveAidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId, + opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int userId, @@ -738,15 +977,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), - 0 /* fingerprintId */, userId, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } }); } @@ -755,17 +990,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( - mContext, mLazyDaemon, userId, mContext.getOpPackageName(), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, callback); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( + mContext, mLazyDaemon, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..c3e5cbe7daf4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub { + + final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining); + } + + @Override + public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) { + onAcquired_2_2(deviceId, acquiredInfo, vendorCode); + } + + @Override + public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onAuthenticated(long deviceId, int fingerId, int groupId, + ArrayList<Byte> token) { + if (fingerId != 0) { + byte[] hardwareAuthToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + mAidlResponseHandler.onAuthenticationSucceeded(fingerId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onError(long deviceId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId}); + } + + @Override + public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId}); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onResetLockout() { + mAidlResponseHandler.onLockoutCleared(); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java index 36d56c8a1544..0730c672acd9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java @@ -89,7 +89,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { // Attempt counter should only be cleared when Keyguard goes away or when // a biometric is successfully authenticated. Lockout should eventually be done below the HAL. // See AuthenticationClient#shouldFrameworkHandleLockout(). - void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { + @Override + public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { if (getLockoutModeForUser(userId) != LOCKOUT_NONE) { Slog.v(TAG, "Reset biometric lockout for user: " + userId + ", clearAttemptCounter: " + clearAttemptCounter); @@ -104,7 +105,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { mLockoutResetCallback.onLockoutReset(userId); } - void addFailedAttemptForUser(int userId) { + @Override + public void addFailedAttemptForUser(int userId) { mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1); mTimedLockoutCleared.put(userId, false); @@ -114,7 +116,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { } @Override - public @LockoutMode int getLockoutModeForUser(int userId) { + @LockoutMode + public int getLockoutModeForUser(int userId) { final int failedAttempts = mFailedAttempts.get(userId, 0); if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) { return LOCKOUT_PERMANENT; @@ -126,6 +129,19 @@ public class LockoutFrameworkImpl implements LockoutTracker { return LOCKOUT_NONE; } + /** + * Clears lockout for Fingerprint HIDL HAL + */ + @Override + public void setLockoutModeForUser(int userId, int mode) { + mFailedAttempts.put(userId, 0); + mTimedLockoutCleared.put(userId, true); + // If we're asked to reset failed attempts externally (i.e. from Keyguard), + // the alarm might still be pending; remove it. + cancelLockoutResetForUser(userId); + mLockoutResetCallback.onLockoutReset(userId); + } + private void cancelLockoutResetForUser(int userId) { mAlarmManager.cancel(getLockoutResetIntentForUser(userId)); } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 8736a53bb9f5..ac7d9c171247 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -221,9 +221,8 @@ public class SyncManager { /** Flags used when connecting to a sync adapter service */ private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS = - Context.BindServiceFlags.of( - Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE - | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT); + Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); /** Singleton instance. */ @GuardedBy("SyncManager.class") diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 472c1f58dc8a..1ac3a12fad21 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -135,7 +135,8 @@ class DeviceStateToLayoutMap { leadDisplayAddress, d.getBrightnessThrottlingMapId(), d.getRefreshRateZoneId(), - d.getRefreshRateThermalThrottlingMapId()); + d.getRefreshRateThermalThrottlingMapId(), + d.getPowerThrottlingMapId()); } layout.postProcessLocked(); } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 098cb87940b2..9f4f78794659 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayViewport; import android.os.IBinder; import android.util.Slog; @@ -201,20 +201,6 @@ abstract class DisplayDevice { * @param state The new display state. * @param brightnessState The new display brightnessState. * @param sdrBrightnessState The new display brightnessState for SDR layers. - * @return A runnable containing work to be deferred until after we have - * exited the critical section, or null if none. - */ - public Runnable requestDisplayStateLocked(int state, float brightnessState, - float sdrBrightnessState) { - return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null); - } - - /** - * Sets the display state, if supported. - * - * @param state The new display state. - * @param brightnessState The new display brightnessState. - * @param sdrBrightnessState The new display brightnessState for SDR layers. * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device. * @return A runnable containing work to be deferred until after we have exited the critical * section, or null if none. @@ -223,7 +209,7 @@ abstract class DisplayDevice { int state, float brightnessState, float sdrBrightnessState, - @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { + @Nullable DisplayOffloadSession displayOffloadSession) { return null; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d372f3031b81..cb2302a60248 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -160,6 +160,7 @@ import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.display.utils.SensorUtils; import com.android.server.input.InputManagerInternal; import com.android.server.utils.FoldSettingProvider; @@ -493,6 +494,7 @@ public final class DisplayManagerService extends SystemService { // If we would like to keep a particular eye on a package, we can set the package name. private final boolean mExtraDisplayEventLogging; + private final String mExtraDisplayLoggingPackageName; private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override @@ -522,6 +524,8 @@ public final class DisplayManagerService extends SystemService { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -555,6 +559,7 @@ public final class DisplayManagerService extends SystemService { mInjector = injector; mContext = context; mFlags = injector.getFlags(); + mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext); mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); @@ -580,8 +585,8 @@ public final class DisplayManagerService extends SystemService { mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); - final String name = DisplayProperties.debug_vri_package().orElse(null); - mExtraDisplayEventLogging = !TextUtils.isEmpty(name); + mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); + mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); } public void setupSchedulerPolicies() { @@ -650,6 +655,7 @@ public final class DisplayManagerService extends SystemService { } mDisplayModeDirector.onBootCompleted(); mLogicalDisplayMapper.onBootCompleted(); + mDisplayNotificationManager.onBootCompleted(); } } @@ -752,7 +758,8 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); - mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); + mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) + ? SmallAreaDetectionController.create(mContext) : null; } @VisibleForTesting @@ -784,6 +791,10 @@ public final class DisplayManagerService extends SystemService { } } + DisplayNotificationManager getDisplayNotificationManager() { + return mDisplayNotificationManager; + } + private void loadStableDisplayValuesLocked() { final Point size = mPersistentDataStore.getStableDisplaySize(); if (size.x > 0 && size.y > 0) { @@ -1776,7 +1787,8 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { // main display adapter registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayDeviceRepo, mFlags)); + mHandler, mDisplayDeviceRepo, mFlags, + mDisplayNotificationManager)); // Standalone VR devices rely on a virtual display as their primary display for // 2D UI. We register virtual display adapter along side the main display adapter @@ -2924,12 +2936,17 @@ public final class DisplayManagerService extends SystemService { // Only send updates outside of DisplayManagerService for enabled displays if (display.isEnabledLocked()) { sendDisplayEventLocked(display, event); + } else if (mExtraDisplayEventLogging) { + Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display); } } private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { int displayId = display.getDisplayIdLocked(); Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + if (mExtraDisplayEventLogging) { + Slog.i(TAG, "Deliver Display Event on Handler: " + event); + } mHandler.sendMessage(msg); } @@ -2957,7 +2974,7 @@ public final class DisplayManagerService extends SystemService { // Check if the target app is in cached mode private boolean isUidCached(int uid) { - if (mActivityManagerInternal == null) { + if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) { return false; } int procState = mActivityManagerInternal.getUidProcessState(uid); @@ -2995,6 +3012,10 @@ public final class DisplayManagerService extends SystemService { // For cached apps, save the pending event until it becomes non-cached synchronized (mPendingCallbackSelfLocked) { PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid); + if (extraLogging(callbackRecord.mPackageName)) { + Slog.i(TAG, + "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback); + } if (pendingCallback == null) { mPendingCallbackSelfLocked.put(uid, new PendingCallback(callbackRecord, displayId, event)); @@ -3009,6 +3030,10 @@ public final class DisplayManagerService extends SystemService { mTempCallbacks.clear(); } + private boolean extraLogging(String packageName) { + return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName); + } + // Runs on Handler thread. // Delivers display group event notifications to callbacks. private void deliverDisplayGroupEvent(int groupId, int event) { @@ -3191,9 +3216,10 @@ public final class DisplayManagerService extends SystemService { LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener, - flags); + flags, displayNotificationManager); } long getDefaultDisplayDelayTimeout() { @@ -3451,6 +3477,7 @@ public final class DisplayManagerService extends SystemService { public final int mUid; private final IDisplayManagerCallback mCallback; private @EventsMask AtomicLong mEventsMask; + private final String mPackageName; public boolean mWifiDisplayScanRequested; @@ -3460,6 +3487,9 @@ public final class DisplayManagerService extends SystemService { mUid = uid; mCallback = callback; mEventsMask = new AtomicLong(eventsMask); + + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + mPackageName = packageNames == null ? null : packageNames[0]; } public void updateEventsMask(@EventsMask long eventsMask) { @@ -3468,7 +3498,8 @@ public final class DisplayManagerService extends SystemService { @Override public void binderDied() { - if (DEBUG) { + if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( + mPackageName)) { Slog.d(TAG, "Display listener for pid " + mPid + " died."); } onCallbackDied(this); @@ -3479,6 +3510,11 @@ public final class DisplayManagerService extends SystemService { */ public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) { if (!shouldSendEvent(event)) { + if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( + mPackageName)) { + Slog.i(TAG, + "Not sending displayEvent: " + event + " due to mask:" + mEventsMask); + } return true; } diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 9b022d8b6662..d97c8e71c73c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -30,6 +30,8 @@ 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 final DisplayManagerService mService; private final DisplayManagerFlags mFlags; @@ -46,6 +48,10 @@ class DisplayManagerShellCommand extends ShellCommand { } final PrintWriter pw = getOutPrintWriter(); switch(cmd) { + case "show-notification": + return showNotification(); + case "cancel-notifications": + return cancelNotifications(); case "set-brightness": return setBrightness(); case "reset-brightness-configuration": @@ -102,6 +108,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(); + pw.println(" show-notification NOTIFICATION_TYPE"); + pw.println(" Show notification for one of the following types: " + NOTIFICATION_TYPES); + pw.println(" cancel-notifications"); + pw.println(" Cancel notifications."); pw.println(" set-brightness BRIGHTNESS"); pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1)."); pw.println(" reset-brightness-configuration"); @@ -172,6 +182,39 @@ class DisplayManagerShellCommand extends ShellCommand { return 0; } + private int showNotification() { + final String notificationType = getNextArg(); + if (notificationType == null) { + getErrPrintWriter().println("Error: no notificationType specified, use one of: " + + NOTIFICATION_TYPES); + return 1; + } + + switch(notificationType) { + 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: " + + NOTIFICATION_TYPES); + return 1; + } + + return 0; + } + + private int cancelNotifications() { + mService.getDisplayNotificationManager().cancelNotifications(); + return 0; + } + private int setBrightness() { String brightnessText = getNextArg(); if (brightnessText == null) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 1652871963b9..7d9c0182a691 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -506,7 +506,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mTag = "DisplayPowerController2[" + mDisplayId + "]"; mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); @@ -566,8 +565,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData( mUniqueDisplayId, mThermalBrightnessThrottlingDataId, - mDisplayDeviceConfig - ), mContext); + logicalDisplay.getPowerThrottlingDataIdLocked(), + mDisplayDeviceConfig), mContext, flags); // Seed the cached brightness saveBrightnessInfo(getScreenBrightnessSetting()); mAutomaticBrightnessStrategy = @@ -821,10 +820,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL; final String thermalBrightnessThrottlingDataId = mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - - mBrightnessClamperController.onDisplayChanged( - new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId, - mThermalBrightnessThrottlingDataId, config)); + final String powerThrottlingDataId = + mLogicalDisplay.getPowerThrottlingDataIdLocked(); mHandler.postAtTime(() -> { boolean changed = false; @@ -858,6 +855,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } mIsDisplayInternal = isDisplayInternal; + // using local variables here, when mBrightnessThrottler is removed, + // mThermalBrightnessThrottlingDataId could be removed as well + // changed = true will be not needed - clampers are maintaining their state and + // will call updatePowerState if needed. + mBrightnessClamperController.onDisplayChanged( + new BrightnessClamperController.DisplayDeviceData(uniqueId, + thermalBrightnessThrottlingDataId, powerThrottlingDataId, config)); + if (changed) { updatePowerState(); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 0a1f316ac059..e5d38cb669d4 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -22,9 +22,8 @@ import static android.view.Display.Mode.INVALID_MODE_ID; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -52,6 +51,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -86,18 +86,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + private Context mOverlayContext; // Called with SyncRoot lock held. LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, - Handler handler, Listener listener, DisplayManagerFlags flags) { - this(syncRoot, context, handler, listener, flags, new Injector()); + Handler handler, Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { + this(syncRoot, context, handler, listener, flags, displayNotificationManager, + new Injector()); } @VisibleForTesting LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, - Listener listener, DisplayManagerFlags flags, Injector injector) { + Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager, + Injector injector) { super(syncRoot, context, handler, listener, TAG); + mDisplayNotificationManager = displayNotificationManager; mInjector = injector; mSurfaceControlProxy = mInjector.getSurfaceControlProxy(); mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport(); @@ -1454,6 +1461,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { + "timestampNanos=" + timestampNanos + ", connectionError=" + connectionError + ")"); } + + mDisplayNotificationManager.onHotplugConnectionError(); } @Override diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index bd82b81513df..3d4209e0d6f3 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -190,6 +190,11 @@ final class LogicalDisplay { private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate; /** + * The ID of the power throttling data that should be used. + */ + private String mPowerThrottlingDataId; + + /** * RefreshRateRange limitation for @Temperature.ThrottlingStatus */ @NonNull @@ -205,6 +210,7 @@ final class LogicalDisplay { mIsEnabled = true; mIsInTransition = false; mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; + mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; } @@ -911,6 +917,25 @@ final class LogicalDisplay { } /** + * @param powerThrottlingDataId The ID of the brightness throttling data that this + * display should use. + */ + public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) { + if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) { + mPowerThrottlingDataId = powerThrottlingDataId; + mDirty = true; + } + } + + /** + * Returns powerThrottlingDataId which is the ID of the brightness + * throttling data that this display should use. + */ + public String getPowerThrottlingDataIdLocked() { + return mPowerThrottlingDataId; + } + + /** * Sets the display of which this display is a follower, regarding brightness or other * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any * others, and has the potential to be a lead display to others. @@ -976,6 +1001,7 @@ final class LogicalDisplay { pw.println("mLeadDisplayId=" + mLeadDisplayId); pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate); pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling); + pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId); } @Override diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index b3b16ade0546..c55bc62f5ae6 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1123,13 +1123,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { displayLayout.getRefreshRateThermalThrottlingMapId() ) ); - setEnabledLocked(newDisplay, displayLayout.isEnabled()); newDisplay.setThermalBrightnessThrottlingDataIdLocked( displayLayout.getThermalBrightnessThrottlingMapId() == null ? DisplayDeviceConfig.DEFAULT_ID : displayLayout.getThermalBrightnessThrottlingMapId()); - + newDisplay.setPowerThrottlingDataIdLocked( + displayLayout.getPowerThrottlingMapId() == null + ? DisplayDeviceConfig.DEFAULT_ID + : displayLayout.getPowerThrottlingMapId()); newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName()); } } diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java index adaa5390cb9b..bf384b02d95e 100644 --- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.util.ArrayMap; @@ -30,15 +31,14 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal; import java.io.PrintWriter; -import java.util.Arrays; import java.util.Map; final class SmallAreaDetectionController { - private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); - private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold); // TODO(b/281720315): Move this to DeviceConfig once server side ready. private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = @@ -47,12 +47,8 @@ final class SmallAreaDetectionController { private final Object mLock = new Object(); private final Context mContext; private final PackageManagerInternal mPackageManager; - private final UserManagerInternal mUserManager; @GuardedBy("mLock") private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); - // TODO(b/298722189): Update allowlist when user changes - @GuardedBy("mLock") - private int[] mUserIds; static SmallAreaDetectionController create(@NonNull Context context) { final SmallAreaDetectionController controller = @@ -67,7 +63,6 @@ final class SmallAreaDetectionController { SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { mContext = context; mPackageManager = LocalServices.getService(PackageManagerInternal.class); - mUserManager = LocalServices.getService(UserManagerInternal.class); deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, BackgroundThread.getExecutor(), new SmallAreaDetectionController.OnPropertiesChangedListener()); @@ -76,6 +71,7 @@ final class SmallAreaDetectionController { @VisibleForTesting void updateAllowlist(@Nullable String property) { + final Map<String, Float> allowPkgMap = new ArrayMap<>(); synchronized (mLock) { mAllowPkgMap.clear(); if (property != null) { @@ -86,8 +82,11 @@ final class SmallAreaDetectionController { .getStringArray(R.array.config_smallAreaDetectionAllowlist); for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); } - updateSmallAreaDetection(); + + if (mAllowPkgMap.isEmpty()) return; + allowPkgMap.putAll(mAllowPkgMap); } + updateSmallAreaDetection(allowPkgMap); } @GuardedBy("mLock") @@ -105,43 +104,32 @@ final class SmallAreaDetectionController { } } - @GuardedBy("mLock") - private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { - for (int i = 0; i < mUserIds.length; i++) { - final int userId = mUserIds[i]; - final int uid = mPackageManager.getPackageUid(pkg, 0, userId); - if (uid > 0) list.put(uid, threshold); - } - } - - @GuardedBy("mLock") - private void updateSmallAreaDetection() { - if (mAllowPkgMap.isEmpty()) return; - - mUserIds = mUserManager.getUserIds(); - - final SparseArray<Float> uidThresholdList = new SparseArray<>(); - for (String pkg : mAllowPkgMap.keySet()) { - final float threshold = mAllowPkgMap.get(pkg); - updateUidListForAllUsers(uidThresholdList, pkg, threshold); + private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) { + final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size()); + for (String pkg : allowPkgMap.keySet()) { + final float threshold = allowPkgMap.get(pkg); + final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg); + if (stage != null) { + appIdThresholdList.put(stage.getAppId(), threshold); + } } - final int[] uids = new int[uidThresholdList.size()]; - final float[] thresholds = new float[uidThresholdList.size()]; - for (int i = 0; i < uidThresholdList.size(); i++) { - uids[i] = uidThresholdList.keyAt(i); - thresholds[i] = uidThresholdList.valueAt(i); + final int[] appIds = new int[appIdThresholdList.size()]; + final float[] thresholds = new float[appIdThresholdList.size()]; + for (int i = 0; i < appIdThresholdList.size(); i++) { + appIds[i] = appIdThresholdList.keyAt(i); + thresholds[i] = appIdThresholdList.valueAt(i); } - updateSmallAreaDetection(uids, thresholds); + updateSmallAreaDetection(appIds, thresholds); } @VisibleForTesting - void updateSmallAreaDetection(int[] uids, float[] thresholds) { - nativeUpdateSmallAreaDetection(uids, thresholds); + void updateSmallAreaDetection(int[] appIds, float[] thresholds) { + nativeUpdateSmallAreaDetection(appIds, thresholds); } - void setSmallAreaDetectionThreshold(int uid, float threshold) { - nativeSetSmallAreaDetectionThreshold(uid, threshold); + void setSmallAreaDetectionThreshold(int appId, float threshold) { + nativeSetSmallAreaDetectionThreshold(appId, threshold); } void dump(PrintWriter pw) { @@ -151,7 +139,6 @@ final class SmallAreaDetectionController { for (String pkg : mAllowPkgMap.keySet()) { pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); } - pw.println(" mUserIds=" + Arrays.toString(mUserIds)); } } @@ -167,11 +154,15 @@ final class SmallAreaDetectionController { private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { @Override public void onPackageAdded(@NonNull String packageName, int uid) { + float threshold = 0.0f; synchronized (mLock) { if (mAllowPkgMap.containsKey(packageName)) { - setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + threshold = mAllowPkgMap.get(packageName); } } + if (threshold > 0.0f) { + setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold); + } } } } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index d910e16de8e8..b0025872aa3d 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; @@ -395,7 +396,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @Override public Runnable requestDisplayStateLocked(int state, float brightnessState, - float sdrBrightnessState) { + float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) { if (state != mDisplayState) { mDisplayState = state; if (state == Display.STATE_OFF) { diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index 54a280fddb6c..68f72d3b085a 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -52,7 +52,8 @@ abstract class BrightnessClamper<T> { abstract void stop(); - enum Type { - THERMAL + protected enum Type { + THERMAL, + POWER } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 14637afe4f76..787f786a97da 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -34,9 +34,12 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.ArrayList; @@ -48,11 +51,9 @@ import java.util.concurrent.Executor; */ public class BrightnessClamperController { private static final String TAG = "BrightnessClamperController"; - private final DeviceConfigParameterProvider mDeviceConfigParameterProvider; private final Handler mHandler; private final ClamperChangeListener mClamperChangeListenerExternal; - private final Executor mExecutor; private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers; @@ -64,13 +65,15 @@ public class BrightnessClamperController { private boolean mClamperApplied = false; public BrightnessClamperController(Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { - this(new Injector(), handler, clamperChangeListener, data, context); + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { + this(new Injector(), handler, clamperChangeListener, data, context, flags); } @VisibleForTesting BrightnessClamperController(Injector injector, Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider(); mHandler = handler; mClamperChangeListenerExternal = clamperChangeListener; @@ -84,7 +87,7 @@ public class BrightnessClamperController { } }; - mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data); + mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags); mModifiers = injector.getModifiers(context); mOnPropertiesChangedListener = properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); @@ -144,6 +147,8 @@ public class BrightnessClamperController { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } else if (mClamperType == Type.THERMAL) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; + } else if (mClamperType == Type.POWER) { + return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; } else { Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType); return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -193,6 +198,7 @@ public class BrightnessClamperController { mClamperType = clamperType; mClamperChangeListenerExternal.onChanged(); } + } private void start() { @@ -219,10 +225,15 @@ public class BrightnessClamperController { } List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data) { + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, + DisplayManagerFlags flags) { List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>(); clampers.add( new BrightnessThermalClamper(handler, clamperChangeListener, data)); + if (flags.isPowerThrottlingClamperEnabled()) { + clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener, + data)); + } return clampers; } @@ -235,21 +246,26 @@ public class BrightnessClamperController { } /** - * Data for clampers + * Config Data for clampers */ - public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData { + public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, + BrightnessPowerClamper.PowerData { @NonNull private final String mUniqueDisplayId; @NonNull private final String mThermalThrottlingDataId; + @NonNull + private final String mPowerThrottlingDataId; private final DisplayDeviceConfig mDisplayDeviceConfig; public DisplayDeviceData(@NonNull String uniqueDisplayId, @NonNull String thermalThrottlingDataId, + @NonNull String powerThrottlingDataId, @NonNull DisplayDeviceConfig displayDeviceConfig) { mUniqueDisplayId = uniqueDisplayId; mThermalThrottlingDataId = thermalThrottlingDataId; + mPowerThrottlingDataId = powerThrottlingDataId; mDisplayDeviceConfig = displayDeviceConfig; } @@ -272,5 +288,24 @@ public class BrightnessClamperController { return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get( mThermalThrottlingDataId); } + + @NonNull + @Override + public String getPowerThrottlingDataId() { + return mPowerThrottlingDataId; + } + + @Nullable + @Override + public PowerThrottlingData getPowerThrottlingData() { + return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get( + mPowerThrottlingDataId); + } + + @Nullable + @Override + public PowerThrottlingConfigData getPowerThrottlingConfigData() { + return mDisplayDeviceConfig.getPowerThrottlingConfigData(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java new file mode 100644 index 000000000000..339b5896f3f7 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java @@ -0,0 +1,295 @@ +/* + * 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.brightness.clamper; + +import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID; +import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.PowerManager; +import android.os.Temperature; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.utils.DeviceConfigParsingUtils; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + + +class BrightnessPowerClamper extends + BrightnessClamper<BrightnessPowerClamper.PowerData> { + + private static final String TAG = "BrightnessPowerClamper"; + @NonNull + private final Injector mInjector; + @NonNull + private final DeviceConfigParameterProvider mConfigParameterProvider; + @NonNull + private final Handler mHandler; + @NonNull + private final ClamperChangeListener mChangeListener; + @Nullable + private PmicMonitor mPmicMonitor; + // data from DeviceConfig, for all displays, for all dataSets + // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData)) + @NonNull + private Map<String, Map<String, PowerThrottlingData>> + mPowerThrottlingDataOverride = Map.of(); + // data from DisplayDeviceConfig, for particular display+dataSet + @Nullable + private PowerThrottlingData mPowerThrottlingDataFromDDC = null; + // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId, + // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC. + @Nullable + private PowerThrottlingData mPowerThrottlingDataActive = null; + @Nullable + private PowerThrottlingConfigData mPowerThrottlingConfigData = null; + + private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE; + private float mCurrentAvgPowerConsumed = 0; + @Nullable + private String mUniqueDisplayId = null; + @Nullable + private String mDataId = null; + + private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { + try { + int status = DeviceConfigParsingUtils.parseThermalStatus(key); + float powerQuota = Float.parseFloat(value); + return new ThrottlingLevel(status, powerQuota); + } catch (IllegalArgumentException iae) { + return null; + } + }; + + private final Function<List<ThrottlingLevel>, PowerThrottlingData> + mDataSetMapper = PowerThrottlingData::create; + + + BrightnessPowerClamper(Handler handler, ClamperChangeListener listener, + PowerData powerData) { + this(new Injector(), handler, listener, powerData); + } + + @VisibleForTesting + BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener, + PowerData powerData) { + mInjector = injector; + mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); + mHandler = handler; + mChangeListener = listener; + + mHandler.post(() -> { + setDisplayData(powerData); + loadOverrideData(); + start(); + }); + + } + + @Override + @NonNull + BrightnessClamper.Type getType() { + return Type.POWER; + } + + @Override + void onDeviceConfigChanged() { + mHandler.post(() -> { + loadOverrideData(); + recalculateActiveData(); + }); + } + + @Override + void onDisplayChanged(PowerData data) { + mHandler.post(() -> { + setDisplayData(data); + recalculateActiveData(); + }); + } + + @Override + void stop() { + if (mPmicMonitor != null) { + mPmicMonitor.shutdown(); + } + } + + /** + * Dumps the state of BrightnessPowerClamper. + */ + public void dump(PrintWriter pw) { + pw.println("BrightnessPowerClamper:"); + pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed); + pw.println(" mUniqueDisplayId=" + mUniqueDisplayId); + pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel); + pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null" + : mPowerThrottlingDataFromDDC.toString())); + super.dump(pw); + } + + private void recalculateActiveData() { + if (mUniqueDisplayId == null || mDataId == null) { + return; + } + mPowerThrottlingDataActive = mPowerThrottlingDataOverride + .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId, + mPowerThrottlingDataFromDDC); + if (mPowerThrottlingDataActive != null) { + if (mPmicMonitor != null) { + mPmicMonitor.stop(); + mPmicMonitor.start(); + } + } else { + if (mPmicMonitor != null) { + mPmicMonitor.stop(); + } + } + recalculateBrightnessCap(); + } + + private void loadOverrideData() { + String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData(); + mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap( + throttlingDataOverride, mDataPointMapper, mDataSetMapper); + } + + private void setDisplayData(@NonNull PowerData data) { + mUniqueDisplayId = data.getUniqueDisplayId(); + mDataId = data.getPowerThrottlingDataId(); + mPowerThrottlingDataFromDDC = data.getPowerThrottlingData(); + if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) { + Slog.wtf(TAG, + "Power throttling data is missing for powerThrottlingDataId=" + mDataId); + } + + mPowerThrottlingConfigData = data.getPowerThrottlingConfigData(); + if (mPowerThrottlingConfigData == null) { + Slog.d(TAG, + "Power throttling data is missing for configuration data."); + } + } + + private void recalculateBrightnessCap() { + boolean isActive = false; + float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX; + float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel); + if (mPowerThrottlingDataActive == null) { + return; + } + if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) { + isActive = true; + // calculate new brightness Cap. + // Brightness has a linear relation to power-consumed. + targetBrightnessCap = + (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX; + // Cap to lowest allowed brightness on device. + targetBrightnessCap = Math.max(targetBrightnessCap, + mPowerThrottlingConfigData.brightnessLowestCapAllowed); + } + + if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) { + mIsActive = isActive; + mBrightnessCap = targetBrightnessCap; + mChangeListener.onChanged(); + } + } + + private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) { + float powerQuota = 0f; + if (mPowerThrottlingDataActive != null) { + // Throttling levels are sorted by increasing severity + for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) { + if (level.thermalStatus <= thermalStatus) { + powerQuota = level.powerQuotaMilliWatts; + } else { + // Throttling levels that are greater than the current status are irrelevant + break; + } + } + } + return powerQuota; + } + + private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) { + mHandler.post(() -> { + mCurrentThermalLevel = thermalStatus; + mCurrentAvgPowerConsumed = avgPowerConsumed; + recalculateBrightnessCap(); + }); + } + + private void start() { + if (mPowerThrottlingConfigData == null) { + return; + } + PowerChangeListener listener = (powerConsumed, thermalStatus) -> { + recalculatePowerQuotaChange(powerConsumed, thermalStatus); + }; + mPmicMonitor = + mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis); + mPmicMonitor.start(); + } + + public interface PowerData { + @NonNull + String getUniqueDisplayId(); + + @NonNull + String getPowerThrottlingDataId(); + + @Nullable + PowerThrottlingData getPowerThrottlingData(); + + @Nullable + PowerThrottlingConfigData getPowerThrottlingConfigData(); + } + + /** + * Power change listener + */ + @FunctionalInterface + public interface PowerChangeListener { + /** + * Notifies that power state changed from power controller. + */ + void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus); + } + + @VisibleForTesting + static class Injector { + PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) { + return new PmicMonitor(listener, pollingTime); + } + + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java new file mode 100644 index 000000000000..26784f2353ff --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java @@ -0,0 +1,192 @@ +/* + * 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.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.IThermalService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Temperature; +import android.power.PowerStatsInternal; +import android.util.IntArray; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Monitors the display consumed power and helps make informed decision, + * regarding overconsumption. + */ +public class PmicMonitor { + private static final String TAG = "PmicMonitor"; + + // The executor to periodically monitor the display power. + private final ScheduledExecutorService mExecutor; + @NonNull + private final PowerChangeListener mPowerChangeListener; + private final long mPowerMonitorPeriodConfigSecs; + private final PowerStatsInternal mPowerStatsInternal; + @VisibleForTesting final IThermalService mThermalService; + private ScheduledFuture<?> mPmicMonitorFuture; + private float mLastEnergyConsumed = 0; + private float mCurrentAvgPower = 0; + private Temperature mCurrentTemperature; + private long mCurrentTimestampMillis = 0; + + PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) { + mPowerChangeListener = listener; + mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class); + mThermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + // start a periodic worker thread. + mExecutor = Executors.newSingleThreadScheduledExecutor(); + mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs; + } + + @Nullable + private Temperature getDisplayTemperature() { + Temperature retTemperature = null; + try { + Temperature[] temperatures; + // TODO b/279114539 Try DISPLAY first and then fallback to SKIN. + temperatures = mThermalService.getCurrentTemperaturesWithType( + Temperature.TYPE_SKIN); + if (temperatures.length > 1) { + Slog.w(TAG, "Multiple skin temperatures not allowed!"); + } + if (temperatures.length > 0) { + retTemperature = temperatures[0]; + } + } catch (RemoteException e) { + Slog.w(TAG, "getDisplayTemperature failed" + e); + } + return retTemperature; + } + + private void capturePeriodicDisplayPower() { + final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo(); + if (energyConsumers == null || energyConsumers.length == 0) { + return; + } + final IntArray energyConsumerIds = new IntArray(); + for (int i = 0; i < energyConsumers.length; i++) { + if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) { + energyConsumerIds.add(energyConsumers[i].id); + } + } + + if (energyConsumerIds.size() == 0) { + Slog.w(TAG, "DISPLAY energyConsumerIds size is null"); + return; + } + CompletableFuture<EnergyConsumerResult[]> futureECRs = + mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray()); + if (futureECRs == null) { + Slog.w(TAG, "Energy consumers results are null"); + return; + } + + EnergyConsumerResult[] displayResults; + try { + displayResults = futureECRs.get(); + } catch (InterruptedException e) { + Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e); + displayResults = null; + } catch (ExecutionException e) { + Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e); + displayResults = null; + } + + if (displayResults == null || displayResults.length == 0) { + Slog.w(TAG, "displayResults are null"); + return; + } + // Support for only 1 display rail. + float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed); + float timeIntervalSeconds = + (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f; + // energy consumed is received in microwatts-seconds. + float currentPower = energyConsumed / timeIntervalSeconds; + // convert power received in microwatts to milliwatts. + currentPower = currentPower / 1000.f; + + // capture thermal state. + Temperature displayTemperature = getDisplayTemperature(); + mCurrentAvgPower = currentPower; + mCurrentTemperature = displayTemperature; + mLastEnergyConsumed = displayResults[0].energyUWs; + mCurrentTimestampMillis = displayResults[0].timestampMs; + if (mCurrentTemperature != null) { + mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus()); + } + } + + /** + * Start polling the power IC. + */ + public void start() { + if (mPowerStatsInternal == null) { + Slog.w(TAG, "Power stats service not found for monitoring."); + return; + } + if (mThermalService == null) { + Slog.w(TAG, "Thermal service not found."); + return; + } + if (mPmicMonitorFuture == null) { + mPmicMonitorFuture = mExecutor.scheduleAtFixedRate( + this::capturePeriodicDisplayPower, + mPowerMonitorPeriodConfigSecs, + mPowerMonitorPeriodConfigSecs, + TimeUnit.SECONDS); + } else { + Slog.e(TAG, "already scheduled, stop() called before start."); + } + } + + /** + * Stop polling to power IC. + */ + public void stop() { + if (mPmicMonitorFuture != null) { + mPmicMonitorFuture.cancel(true); + mPmicMonitorFuture = null; + } + } + + /** + * Shutdown power IC service and worker thread. + */ + public void shutdown() { + mExecutor.shutdownNow(); + } +} 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 03b0cfc3d844..e3aa161f001a 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/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java index 23ffe5948da8..465584c3d90c 100644 --- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -77,6 +77,12 @@ public class DeviceConfigParameterProvider { // Test parameters // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 + // allows to customize power throttling data + public String getPowerThrottlingData() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null); + } + // allows to customize brightness throttling data public String getBrightnessThrottlingData() { return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index a5e3b70802ef..d953e8e52365 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -59,24 +59,43 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, Flags::enableModeLimitForExternalDisplay); + private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState( + Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING, + Flags::enableConnectedDisplayErrorHandling); + private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState( Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE, Flags::backUpSmoothDisplayAndForcePeakRefreshRate); + private final FlagState mPowerThrottlingClamperFlagState = new FlagState( + Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER, + Flags::enablePowerThrottlingClamper); + + private final FlagState mSmallAreaDetectionFlagState = new FlagState( + Flags.FLAG_ENABLE_SMALL_AREA_DETECTION, + Flags::enableSmallAreaDetection); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); } - /** Returns whether hdr clamper is enabled on not*/ + /** Returns whether NBM Controller is enabled or not. */ public boolean isNbmControllerEnabled() { return mNbmControllerFlagState.isEnabled(); } + /** Returns whether hdr clamper is enabled on not. */ public boolean isHdrClamperEnabled() { return mHdrClamperFlagState.isEnabled(); } + /** Returns whether power throttling clamper is enabled on not. */ + public boolean isPowerThrottlingClamperEnabled() { + return mPowerThrottlingClamperFlagState.isEnabled(); + } + + /** * Returns whether adaptive tone improvements are enabled */ @@ -123,10 +142,19 @@ public class DisplayManagerFlags { return mDisplayOffloadFlagState.isEnabled(); } + /** Returns whether error notifications for connected displays are enabled on not */ + public boolean isConnectedDisplayErrorHandlingEnabled() { + return mConnectedDisplayErrorHandlingFlagState.isEnabled(); + } + public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() { return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); } + public boolean isSmallAreaDetectionEnabled() { + return mSmallAreaDetectionFlagState.isEnabled(); + } + private static class FlagState { private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 3d203fb7427f..9141814da6fc 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -27,6 +27,14 @@ flag { } flag { + name: "enable_power_throttling_clamper" + namespace: "display_manager" + description: "Feature flag for Power Throttling Clamper" + bug: "294777007" + is_fixed_read_only: true +} + +flag { name: "enable_adaptive_tone_improvements_1" namespace: "display_manager" description: "Feature flag for Adaptive Tone Improvements" @@ -82,9 +90,26 @@ flag { } flag { + name: "enable_connected_display_error_handling" + namespace: "display_manager" + description: "Feature flag for connected display error handling" + bug: "283461472" + is_fixed_read_only: true +} + +flag { name: "back_up_smooth_display_and_force_peak_refresh_rate" namespace: "display_manager" description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate" bug: "211737588" is_fixed_read_only: true } + +flag { + name: "enable_small_area_detection" + namespace: "display_manager" + description: "Feature flag for SmallAreaDetection" + bug: "298722189" + is_fixed_read_only: true +} + diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index d9ec3de46b0e..40cb3303adda 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -80,7 +80,8 @@ public class Layout { createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true, DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null); } /** @@ -97,6 +98,7 @@ public class Layout { * @param refreshRateZoneId Layout limited refresh rate zone name. * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling * policy should be used. + * @param powerThrottlingMapId Name of which power throttling policy should be used. * * @exception IllegalArgumentException When a default display owns a display group other than * DEFAULT_DISPLAY_GROUP. @@ -106,7 +108,8 @@ public class Layout { String displayGroupName, DisplayIdProducer idProducer, int position, @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, @Nullable String refreshRateZoneId, - @Nullable String refreshRateThermalThrottlingMapId) { + @Nullable String refreshRateThermalThrottlingMapId, + @Nullable String powerThrottlingMapId) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return; @@ -139,7 +142,7 @@ public class Layout { final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName, brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, - refreshRateThermalThrottlingMapId); + refreshRateThermalThrottlingMapId, powerThrottlingMapId); mDisplays.add(display); } @@ -311,6 +314,9 @@ public class Layout { @Nullable private final String mThermalRefreshRateThrottlingMapId; + @Nullable + private final String mPowerThrottlingMapId; + // The ID of the lead display that this display will follow in a layout. -1 means no lead. // This is determined using {@code mLeadDisplayAddress}. private int mLeadDisplayId; @@ -318,7 +324,8 @@ public class Layout { private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, - @Nullable String refreshRateThermalThrottlingMapId) { + @Nullable String refreshRateThermalThrottlingMapId, + @Nullable String powerThrottlingMapId) { mAddress = address; mLogicalDisplayId = logicalDisplayId; mIsEnabled = isEnabled; @@ -328,6 +335,7 @@ public class Layout { mLeadDisplayAddress = leadDisplayAddress; mRefreshRateZoneId = refreshRateZoneId; mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; + mPowerThrottlingMapId = powerThrottlingMapId; mLeadDisplayId = NO_LEAD_DISPLAY; } @@ -344,6 +352,7 @@ public class Layout { + ", mLeadDisplayId: " + mLeadDisplayId + ", mLeadDisplayAddress: " + mLeadDisplayAddress + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId + + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId + "}"; } @@ -366,7 +375,9 @@ public class Layout { && this.mLeadDisplayId == otherDisplay.mLeadDisplayId && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress) && Objects.equals(mThermalRefreshRateThrottlingMapId, - otherDisplay.mThermalRefreshRateThrottlingMapId); + otherDisplay.mThermalRefreshRateThrottlingMapId) + && Objects.equals(mPowerThrottlingMapId, + otherDisplay.mPowerThrottlingMapId); } @Override @@ -382,6 +393,7 @@ public class Layout { result = 31 * result + mLeadDisplayId; result = 31 * result + Objects.hashCode(mLeadDisplayAddress); result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); + result = 31 * result + Objects.hashCode(mPowerThrottlingMapId); return result; } @@ -441,6 +453,15 @@ public class Layout { return mThermalRefreshRateThrottlingMapId; } + /** + * Gets the id of the power throttling map that should be used. + * @return The ID of the power throttling map that this display should use, + * null if unspecified, will fall back to default. + */ + public String getPowerThrottlingMapId() { + return mPowerThrottlingMapId; + } + private void setLeadDisplayId(int id) { mLeadDisplayId = id; } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index ca23844044ca..d023913c9694 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -723,7 +723,9 @@ public class DisplayModeDirector { if (mode.getPhysicalWidth() > maxAllowedWidth || mode.getPhysicalHeight() > maxAllowedHeight || mode.getPhysicalWidth() < outSummary.minWidth - || mode.getPhysicalHeight() < outSummary.minHeight) { + || mode.getPhysicalHeight() < outSummary.minHeight + || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate + || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) { continue; } diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java new file mode 100644 index 000000000000..f683e8104889 --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java @@ -0,0 +1,132 @@ +/* + * 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 new file mode 100644 index 000000000000..5cdef38cd45c --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java @@ -0,0 +1,205 @@ +/* + * 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.app.Notification.COLOR_DEFAULT; +import static com.android.internal.notification.SystemNotificationChannels.ALERTS; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; + +/** + * Manages notifications for {@link com.android.server.display.DisplayManagerService}. + */ +public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener { + /** 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"; + private static final String NOTIFICATION_GROUP_NAME = TAG; + private static final String DISPLAY_NOTIFICATION_TAG = TAG; + private static final int DISPLAY_NOTIFICATION_ID = 1; + private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L; + + private final Injector mInjector; + 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); + } + }); + } + + @VisibleForTesting + DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, + final Injector injector) { + mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled(); + mContext = context; + mInjector = injector; + } + + /** + * Initialize services, which may be not yet published during boot. + * see {@link android.os.ServiceManager.ServiceNotFoundException}. + */ + public void onBootCompleted() { + mNotificationManager = mInjector.getNotificationManager(); + if (mNotificationManager == null) { + 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)); + } + + /** + * Send notification about hotplug connection error. + */ + public void onHotplugConnectionError() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onHotplugConnectionError:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content)); + } + + /** + * Cancel sent notifications. + */ + public void cancelNotifications() { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null"); + return; + } + + mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID); + } + + /** + * Send generic error notification. + */ + @SuppressLint("AndroidFrameworkRequiresPermission") + private void sendErrorNotification(final Notification notification) { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null"); + return; + } + + mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID, + notification); + } + + /** + * @return a newly built notification about an issue with connected display. + */ + private Notification createErrorNotification(final int titleId, final int messageId) { + final Resources resources = mContext.getResources(); + final CharSequence title = resources.getText(titleId); + final CharSequence message = resources.getText(messageId); + + int color = COLOR_DEFAULT; + try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) { + color = attrs.getColor(0, color); + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "colorError attribute is not found: " + e.getMessage()); + } + + return new Notification.Builder(mContext, ALERTS) + .setGroup(NOTIFICATION_GROUP_NAME) + .setSmallIcon(R.drawable.usb_cable_unknown_issue) + .setWhen(0) + .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC) + .setOngoing(false) + .setTicker(title) + .setColor(color) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 429db5eeed49..c28a68b70d1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4951,6 +4951,11 @@ public class HdmiControlService extends SystemService { AudioDeviceAttributes attributes = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", new ArrayList<AudioProfile>(), audioDescriptors); + // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence + // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state. + if (!isCecControlEnabled()) { + setSystemAudioActivated(true); + } getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); } diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index b090334db352..27e1b9a303f2 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -60,84 +60,99 @@ public final class ImeTrackerService extends IImeTracker.Stub { private static final long TIMEOUT_MS = 10_000; /** Handler for registering timeouts for live entries. */ + @GuardedBy("mLock") private final Handler mHandler; /** Singleton instance of the History. */ - @GuardedBy("ImeTrackerService.this") + @GuardedBy("mLock") private final History mHistory = new History(); + private final Object mLock = new Object(); + ImeTrackerService(@NonNull Looper looper) { mHandler = new Handler(looper, null /* callback */, true /* async */); } @NonNull @Override - public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + public ImeTracker.Token onRequestShow(@NonNull String tag, int uid, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final var binder = new Binder(); final var token = new ImeTracker.Token(binder, tag); final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN, origin, reason); - mHistory.addEntry(binder, entry); - - // Register a delayed task to handle the case where the new entry times out. - mHandler.postDelayed(() -> { - synchronized (ImeTrackerService.this) { - mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); - } - }, TIMEOUT_MS); - + synchronized (mLock) { + mHistory.addEntry(binder, entry); + + // Register a delayed task to handle the case where the new entry times out. + mHandler.postDelayed(() -> { + synchronized (mLock) { + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, + ImeTracker.PHASE_NOT_SET); + } + }, TIMEOUT_MS); + } return token; } @NonNull @Override - public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + public ImeTracker.Token onRequestHide(@NonNull String tag, int uid, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final var binder = new Binder(); final var token = new ImeTracker.Token(binder, tag); final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN, origin, reason); - mHistory.addEntry(binder, entry); - - // Register a delayed task to handle the case where the new entry times out. - mHandler.postDelayed(() -> { - synchronized (ImeTrackerService.this) { - mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); - } - }, TIMEOUT_MS); - + synchronized (mLock) { + mHistory.addEntry(binder, entry); + + // Register a delayed task to handle the case where the new entry times out. + mHandler.postDelayed(() -> { + synchronized (mLock) { + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, + ImeTracker.PHASE_NOT_SET); + } + }, TIMEOUT_MS); + } return token; } @Override - public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { - final var entry = mHistory.getEntry(binder); - if (entry == null) return; + public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { + synchronized (mLock) { + final var entry = mHistory.getEntry(binder); + if (entry == null) return; - entry.mPhase = phase; + entry.mPhase = phase; + } } @Override - public synchronized void onFailed(@NonNull ImeTracker.Token statsToken, - @ImeTracker.Phase int phase) { - mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); + public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); + } } @Override - public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken, - @ImeTracker.Phase int phase) { - mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); + public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); + } } @Override - public synchronized void onShown(@NonNull ImeTracker.Token statsToken) { - mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + public void onShown(@NonNull ImeTracker.Token statsToken) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + } } @Override - public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) { - mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + public void onHidden(@NonNull ImeTracker.Token statsToken) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + } } /** @@ -146,25 +161,30 @@ public final class ImeTrackerService extends IImeTracker.Stub { * @param statsToken the token corresponding to the current IME request. * @param requestWindowName the name of the window that created the IME request. */ - public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken, + public void onImmsUpdate(@NonNull ImeTracker.Token statsToken, @NonNull String requestWindowName) { - final var entry = mHistory.getEntry(statsToken.getBinder()); - if (entry == null) return; + synchronized (mLock) { + final var entry = mHistory.getEntry(statsToken.getBinder()); + if (entry == null) return; - entry.mRequestWindowName = requestWindowName; + entry.mRequestWindowName = requestWindowName; + } } /** Dumps the contents of the history. */ - public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) { - mHistory.dump(pw, prefix); + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + synchronized (mLock) { + mHistory.dump(pw, prefix); + } } @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) @Override - public synchronized boolean hasPendingImeVisibilityRequests() { + public boolean hasPendingImeVisibilityRequests() { super.hasPendingImeVisibilityRequests_enforcePermission(); - - return !mHistory.mLiveEntries.isEmpty(); + synchronized (mLock) { + return !mHistory.mLiveEntries.isEmpty(); + } } /** diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index d4578dc1f74a..4851a81d3b69 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -486,9 +486,12 @@ public class LocaleManagerService extends SystemService { Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { - String inputMethodPkgName = ComponentName - .unflattenFromString(currentInputMethod) - .getPackageName(); + ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod); + if (componentName == null) { + Slog.d(TAG, "inValid input method"); + return false; + } + String inputMethodPkgName = componentName.getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 1c5ecb70a3ae..1e8b387fc189 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/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java index eb997badca52..8bc69c226d1a 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -345,7 +345,7 @@ import java.util.Set; } private void notifyBluetoothRoutesUpdated() { - mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); + mListener.onBluetoothRoutesUpdated(); } private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 93f6ff3de3c2..33190ade4f42 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -231,7 +231,7 @@ import java.util.Objects; } if (isDeviceRouteChanged) { - mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + mOnDeviceRouteChangedListener.onDeviceRouteChanged(); } } } diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java index ddeeacc76579..2b01001fd7d1 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteController.java +++ b/services/core/java/com/android/server/media/BluetoothRouteController.java @@ -136,12 +136,8 @@ import java.util.Objects; */ interface BluetoothRoutesUpdatedListener { - /** - * Called when Bluetooth routes have changed. - * - * @param routes updated Bluetooth routes list. - */ - void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes); + /** Called when Bluetooth routes have changed. */ + void onBluetoothRoutesUpdated(); } /** diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index e17f4a3fd42f..7876095a548a 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -93,12 +93,8 @@ import com.android.media.flags.Flags; */ interface OnDeviceRouteChangedListener { - /** - * Called when device route has changed. - * - * @param deviceRoute non-null device route. - */ - void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute); + /** Called when device route has changed. */ + void onDeviceRouteChanged(); } } diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index 198040378dc6..ba3cecf7c091 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -280,7 +280,7 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { private void notifyBluetoothRoutesUpdated() { if (mListener != null) { - mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); + mListener.onBluetoothRoutesUpdated(); } } diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index 971d11f24b9c..6ba40ae33f3c 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -165,8 +165,8 @@ import java.util.Objects; } } - private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { - mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + private void notifyDeviceRouteUpdate() { + mOnDeviceRouteChangedListener.onDeviceRouteChanged(); } private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { @@ -177,7 +177,7 @@ import java.util.Objects; synchronized (LegacyDeviceRouteController.this) { mDeviceRoute = deviceRoute; } - notifyDeviceRouteUpdate(deviceRoute); + notifyDeviceRouteUpdate(); } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index cc261a4b9797..44719f88351b 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -515,8 +515,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager, - String packageName) { + public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); boolean setDeviceRouteSelected = false; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 95ca08cc7fe9..a158b18d91b4 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 803ab28f2a65..0e8f90795ef9 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/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6c9aa4b0d849..67a1ccd17835 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -109,21 +109,28 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> { - publishProviderState(); - if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); - } - }); - - mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> { - mHandler.post(() -> { - publishProviderState(); - if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); - } - }); - }); + mBluetoothRouteController = + BluetoothRouteController.createInstance( + context, + () -> { + publishProviderState(); + if (updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } + }); + + mDeviceRouteController = + DeviceRouteController.createInstance( + context, + () -> { + mHandler.post( + () -> { + publishProviderState(); + if (updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } + }); + }); mAudioManager.addOnDevicesForAttributesChangedListener( AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(), 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 13d166294603..8cbc368467bb 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; @@ -818,6 +829,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; final long token = Binder.clearCallingIdentity(); 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 000000000000..f18ecad09c42 --- /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/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java new file mode 100644 index 000000000000..ff70cb35f9dc --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java @@ -0,0 +1,91 @@ +/* + * 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 android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; + +public class MediaProjectionSessionIdGenerator { + + private static final String PREFERENCES_FILE_NAME = "media_projection_session_id"; + private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key"; + private static final int SESSION_ID_DEFAULT_VALUE = 0; + + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static MediaProjectionSessionIdGenerator sInstance; + + private final Object mSessionIdLock = new Object(); + + @GuardedBy("mSessionIdLock") + private final SharedPreferences mSharedPreferences; + + /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */ + public static MediaProjectionSessionIdGenerator getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + File preferencesFile = + new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + SharedPreferences preferences = + context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + sInstance = new MediaProjectionSessionIdGenerator(preferences); + } + return sInstance; + } + } + + @VisibleForTesting + public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) { + this.mSharedPreferences = sharedPreferences; + } + + /** Returns the current session ID. This value is persisted across reboots. */ + public int getCurrentSessionId() { + synchronized (mSessionIdLock) { + return getCurrentSessionIdInternal(); + } + } + + /** + * Creates and returns a new session ID. This value will be persisted as the new current session + * ID, and will be persisted across reboots. + */ + public int createAndGetNewSessionId() { + synchronized (mSessionIdLock) { + int newSessionId = getCurrentSessionId() + 1; + setSessionIdInternal(newSessionId); + return newSessionId; + } + } + + @GuardedBy("mSessionIdLock") + private void setSessionIdInternal(int value) { + mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply(); + } + + @GuardedBy("mSessionIdLock") + private int getCurrentSessionIdInternal() { + return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE); + } +} diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java new file mode 100644 index 000000000000..4026d0c43484 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java @@ -0,0 +1,109 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; + +/** Stores timestamps of media projection sessions. */ +public class MediaProjectionTimestampStore { + private static final String PREFERENCES_FILE_NAME = "media_projection_timestamp"; + private static final String TIMESTAMP_PREF_KEY = "media_projection_timestamp_key"; + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static MediaProjectionTimestampStore sInstance; + + private final Object mTimestampLock = new Object(); + + @GuardedBy("mTimestampLock") + private final SharedPreferences mSharedPreferences; + + private final InstantSource mInstantSource; + + @VisibleForTesting + public MediaProjectionTimestampStore( + SharedPreferences sharedPreferences, InstantSource instantSource) { + this.mSharedPreferences = sharedPreferences; + this.mInstantSource = instantSource; + } + + /** Creates or returns an existing instance of {@link MediaProjectionTimestampStore}. */ + public static MediaProjectionTimestampStore getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + File preferencesFile = + new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + SharedPreferences preferences = + context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system()); + } + return sInstance; + } + } + + /** + * Returns the time that has passed since the last active session, or {@code null} if there was + * no last active session. + */ + @Nullable + public Duration timeSinceLastActiveSession() { + synchronized (mTimestampLock) { + Instant lastActiveSessionTimestamp = getLastActiveSessionTimestamp(); + if (lastActiveSessionTimestamp == null) { + return null; + } + Instant now = mInstantSource.instant(); + return Duration.between(lastActiveSessionTimestamp, now); + } + } + + /** Registers that the current active session ended now. */ + public void registerActiveSessionEnded() { + synchronized (mTimestampLock) { + Instant now = mInstantSource.instant(); + setLastActiveSessionTimestamp(now); + } + } + + @GuardedBy("mTimestampLock") + @Nullable + private Instant getLastActiveSessionTimestamp() { + long lastActiveSessionEpochMilli = + mSharedPreferences.getLong(TIMESTAMP_PREF_KEY, /* defValue= */ -1); + if (lastActiveSessionEpochMilli == -1) { + return null; + } + return Instant.ofEpochMilli(lastActiveSessionEpochMilli); + } + + @GuardedBy("mTimestampLock") + private void setLastActiveSessionTimestamp(@NonNull Instant timestamp) { + mSharedPreferences.edit().putLong(TIMESTAMP_PREF_KEY, timestamp.toEpochMilli()).apply(); + } +} diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index 4c6110b99efd..d00268899a07 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 c7c8136ff0da..79c13461fc6b 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -569,6 +569,10 @@ public final class NativeTombstoneManager { @Override public void onEvent(int event, @Nullable String path) { + if (path == null) { + Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()"); + return; + } mHandler.post(() -> { // Ignore .tmp files. if (path.endsWith(".tmp")) { diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS new file mode 100644 index 000000000000..6f322eea0f2e --- /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 index 21fa9f9a9401..66ad7169d6ec 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 index 754a7ede8006..b006ac862533 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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 7db7bf538c37..30017be96085 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -505,6 +505,10 @@ public class ComputerEngine implements Computer { int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { if (!mUserManager.exists(userId)) return Collections.emptyList(); + + // Allow to match activities of quarantined packages. + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; + final String instantAppPkgName = getInstantAppPackageName(filterCallingUid); enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission */, false /* checkShell */, @@ -647,11 +651,6 @@ public class ComputerEngine implements Computer { flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); - // Only if the query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } Intent originalIntent = null; ComponentName comp = intent.getComponent(); if (comp == null) { @@ -4047,9 +4046,6 @@ public class ComputerEngine implements Computer { flags = updateFlagsForComponent(flags, userId); enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, false /* checkShell */, "get provider info"); - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } ParsedProvider p = mComponentResolver.getProvider(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); @@ -4679,9 +4675,6 @@ public class ComputerEngine implements Computer { int callingUid) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags, userId); boolean checkedGrants = false; @@ -4794,13 +4787,6 @@ public class ComputerEngine implements Computer { false /* checkShell */, "queryContentProviders"); if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList(); flags = updateFlagsForComponent(flags, userId); - - // Only if the service query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } - ArrayList<ProviderInfo> finalList = null; final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName, metaDataKey, uid, flags, userId); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index edb45aa0ffb4..29678181679b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1269,10 +1269,9 @@ final class InstallPackageHelper { replace = true; if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName); } - final AndroidPackage oldPackage = mPm.mPackages.get(pkgName); - if (replace && oldPackage != null) { + if (replace) { // Prevent apps opting out from runtime permissions - final int oldTargetSdk = oldPackage.getTargetSdkVersion(); + final int oldTargetSdk = ps.getTargetSdkVersion(); final int newTargetSdk = parsedPackage.getTargetSdkVersion(); if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1 && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) { @@ -1284,10 +1283,10 @@ final class InstallPackageHelper { + " target SDK " + oldTargetSdk + " does."); } // Prevent persistent apps from being updated - if (oldPackage.isPersistent() + if (ps.isPersistent() && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) { throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK, - "Package " + oldPackage.getPackageName() + " is a persistent app. " + "Package " + pkgName + " is a persistent app. " + "Persistent apps are not updateable."); } } @@ -1668,7 +1667,7 @@ final class InstallPackageHelper { } // don't allow a system upgrade unless the upgrade hash matches - if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null + if (oldPackageState.getRestrictUpdateHash() != null && oldPackageState.isSystem()) { final byte[] digestBytes; try { @@ -1684,12 +1683,13 @@ final class InstallPackageHelper { throw new PrepareFailure(INSTALL_FAILED_INVALID_APK, "Could not compute hash: " + pkgName11); } - if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) { + if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) { throw new PrepareFailure(INSTALL_FAILED_INVALID_APK, "New package fails restrict-update check: " + pkgName11); } // retain upgrade restriction - parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash()); + parsedPackage.setRestrictUpdateHash( + oldPackageState.getRestrictUpdateHash()); } if (oldPackage != null) { diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 2d192826ba9a..7d822b50a293 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.Process.INVALID_UID; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; import static com.android.server.pm.PackageManagerService.TAG; @@ -32,6 +34,7 @@ import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; +import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -56,6 +59,7 @@ import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; final class InstallRequest { @@ -147,6 +151,9 @@ final class InstallRequest { @NonNull private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY; + @NonNull + private ArrayList<String> mWarnings = new ArrayList<>(); + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -658,6 +665,11 @@ final class InstallRequest { return mUpdateBroadcastInstantUserIds; } + @NonNull + public ArrayList<String> getWarnings() { + return mWarnings; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } @@ -855,6 +867,10 @@ final class InstallRequest { } } + public void addWarning(@NonNull String warning) { + mWarnings.add(warning); + } + public void onPrepareStarted() { if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE); @@ -904,22 +920,37 @@ final class InstallRequest { } public void onDexoptFinished(DexoptResult dexoptResult) { - if (mPackageMetrics == null) { - return; - } - mDexoptStatus = dexoptResult.getFinalStatus(); - if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) { - return; + // Only report external profile warnings when installing from adb. The goal is to warn app + // developers if they have provided bad external profiles, so it's not beneficial to report + // those warnings in the normal app install workflow. + if (isInstallFromAdb() && Flags.useArtServiceV2()) { + var externalProfileErrors = new LinkedHashSet<String>(); + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + externalProfileErrors.addAll(fileResult.getExternalProfileErrors()); + } + } + if (!externalProfileErrors.isEmpty()) { + addWarning("Error occurred during dexopt when processing external profiles:\n " + + String.join("\n ", externalProfileErrors)); + } } - long durationMillis = 0; - for (DexoptResult.PackageDexoptResult packageResult : - dexoptResult.getPackageDexoptResults()) { - for (DexoptResult.DexContainerFileDexoptResult fileResult : - packageResult.getDexContainerFileDexoptResults()) { - durationMillis += fileResult.getDex2oatWallTimeMillis(); + + // Report dexopt metrics. + if (mPackageMetrics != null) { + mDexoptStatus = dexoptResult.getFinalStatus(); + if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) { + long durationMillis = 0; + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + durationMillis += fileResult.getDex2oatWallTimeMillis(); + } + } + mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } } - mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } public void onInstallCompleted() { diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 148e0df7cf64..9ad8318c2b5f 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -89,6 +89,21 @@ public final class MovePackageHelper { if (packageState == null || packageState.getPkg() == null) { throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); } + final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, + mPm.mUserManager.getUserIds(), true); + final UserHandle userForMove; + if (installedUserIds.length > 0) { + userForMove = UserHandle.of(installedUserIds[0]); + } else { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, + "Package is not installed for any user"); + } + for (int userId : installedUserIds) { + if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid, + userId)) { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); + } + } final AndroidPackage pkg = packageState.getPkg(); if (packageState.isSystem()) { throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, @@ -134,8 +149,6 @@ public final class MovePackageHelper { final String label = String.valueOf(pm.getApplicationLabel( AndroidPackageUtils.generateAppInfoWithoutState(pkg))); final int targetSdkVersion = pkg.getTargetSdkVersion(); - final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, - mPm.mUserManager.getUserIds(), true); final String fromCodePath; if (codeFile.getParentFile().getName().startsWith( PackageManagerService.RANDOM_DIR_PREFIX)) { @@ -303,8 +316,8 @@ public final class MovePackageHelper { final PackageLite lite = ret.isSuccess() ? ret.getResult() : null; final InstallingSession installingSession = new InstallingSession(origin, move, installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource, - volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, - lite, mPm); + volumeUuid, userForMove, packageAbiOverride, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm); installingSession.movePackage(); } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index f59188e9fd93..0fb1f7a0780c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -196,8 +196,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.length; i < size; ++i) { var mainActivity = mainActivities[i]; Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.title, iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.title, + mainActivity.originalComponentName, + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -215,8 +219,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); Path iconPath = storeIcon(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.getLabel().toString(), iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.getLabel().toString(), + mainActivity.getComponentName(), + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -593,6 +601,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getTitle(); + archivedActivity.originalComponentName = info.getOriginalComponentName(); archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap()); archivedActivity.monochromeIconBitmap = bytesFromBitmapFile( info.getMonochromeIconBitmap()); @@ -624,6 +633,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getLabel().toString(); + archivedActivity.originalComponentName = info.getComponentName(); archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap( drawableToBitmap(info.getIcon(/* density= */ 0))); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 662703992ad8..d0e5f96f8d0f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2930,15 +2930,40 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @return a future that will be completed when the whole process is completed. */ private CompletableFuture<Void> install() { + // `futures` either contains only one session (`this`) or contains one parent session + // (`this`) and n-1 child sessions. List<CompletableFuture<InstallResult>> futures = installNonStaged(); CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()]; return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> { if (t == null) { setSessionApplied(); + var multiPackageWarnings = new ArrayList<String>(); + if (isMultiPackage()) { + // This is a parent session. Collect warnings from children. + for (CompletableFuture<InstallResult> f : futures) { + InstallResult result = f.join(); + if (result.session != this && result.extras != null) { + ArrayList<String> childWarnings = result.extras.getStringArrayList( + PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(childWarnings)) { + multiPackageWarnings.addAll(childWarnings); + } + } + } + } for (CompletableFuture<InstallResult> f : futures) { InstallResult result = f.join(); + Bundle extras = result.extras; + if (isMultiPackage() && result.session == this + && !multiPackageWarnings.isEmpty()) { + if (extras == null) { + extras = new Bundle(); + } + extras.putStringArrayList( + PackageInstaller.EXTRA_WARNINGS, multiPackageWarnings); + } result.session.dispatchSessionFinished( - INSTALL_SUCCEEDED, "Session installed", result.extras); + INSTALL_SUCCEEDED, "Session installed", extras); } } else { PackageManagerException e = (PackageManagerException) t.getCause(); @@ -5189,6 +5214,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!TextUtils.isEmpty(existing)) { fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); } + ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(warnings)) { + fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings); + } } try { final BroadcastOptions options = BroadcastOptions.makeBasic(); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 651845e71924..e7499680b9a2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -747,7 +747,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId, - @NonNull String recentCallingPackage, @NonNull String debugInfo) { + @Nullable String recentCallingPackage, @NonNull String debugInfo) { mService.notifyComponentUsed(snapshot(), packageName, userId, recentCallingPackage, debugInfo); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6260dd583bf9..839b6998bd8b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -36,6 +36,7 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; +import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE; import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME; @@ -164,6 +165,7 @@ import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.ExceptionUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -1434,6 +1436,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService break; } } + if (!request.getWarnings().isEmpty()) { + extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings()); + } return extras; } @@ -1466,8 +1471,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService archPkg.versionCodeMajor = (int) (longVersionCode >> 32); archPkg.versionCode = (int) longVersionCode; - // TODO(b/297916136): extract target sdk version. - archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK; + archPkg.targetSdkVersion = ps.getTargetSdkVersion(); // These get translated in flags important for user data management. archPkg.defaultToDeviceProtectedStorage = String.valueOf( @@ -4574,7 +4578,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, + @UserIdInt int userId, @Nullable String recentCallingPackage, @NonNull String debugInfo) { synchronized (mLock) { final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); @@ -6131,8 +6135,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService final Computer snapshot = snapshotComputer(); enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId, "setPackagesSuspendedAsUser"); - boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) - && Flags.quarantinedEnabled(); + boolean quarantined = false; + if (Flags.quarantinedEnabled()) { + if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) { + quarantined = true; + } else if (FeatureFlagUtils.isEnabled(mContext, + SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) { + final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing); + quarantined = callingPackage.equals(wellbeingPkg); + } + } return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid, false /* forQuietMode */, quarantined); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3a9272dc2003..7264e2eff4aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4397,10 +4397,21 @@ class PackageManagerShellCommand extends ShellCommand { session.commit(receiver.getIntentSender()); if (!session.isStaged()) { final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); + int status = result.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + List<String> warnings = + result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS); if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { + if (!ArrayUtils.isEmpty(warnings)) { + // Don't start the output string with "Success" because that will make adb + // treat this as a success. + for (String warning : warnings) { + pw.println("Warning: " + warning); + } + // Treat warnings as failure to draw app developers' attention. + status = PackageInstaller.STATUS_FAILURE; + pw.println("Completed with warning(s)"); + } else if (logSuccess) { pw.println("Success"); } } else { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 2e6006465bd9..3ca933a66656 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -99,6 +99,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1; private static final int UPDATE_AVAILABLE = 1 << 2; private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3; + private static final int PERSISTENT = 1 << 4; } private int mBooleans; @@ -217,6 +218,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Nullable private String mAppMetadataFilePath; + private int mTargetSdkVersion; + + @Nullable + private byte[] mRestrictUpdateHash; + /** * Snapshot support. */ @@ -535,6 +541,24 @@ public class PackageSetting extends SettingBase implements PackageStateInternal onChanged(); } + public PackageSetting setIsPersistent(boolean isPersistent) { + setBoolean(Booleans.PERSISTENT, isPersistent); + onChanged(); + return this; + } + + public PackageSetting setTargetSdkVersion(int targetSdkVersion) { + mTargetSdkVersion = targetSdkVersion; + onChanged(); + return this; + } + + public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) { + mRestrictUpdateHash = restrictUpdateHash; + onChanged(); + return this; + } + @Override public int getSharedUserAppId() { return mSharedUserAppId; @@ -701,6 +725,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal categoryOverride = other.categoryOverride; mDomainSetId = other.mDomainSetId; mAppMetadataFilePath = other.mAppMetadataFilePath; + mTargetSdkVersion = other.mTargetSdkVersion; + mRestrictUpdateHash = other.mRestrictUpdateHash == null + ? null : other.mRestrictUpdateHash.clone(); usesSdkLibraries = other.usesSdkLibraries != null ? Arrays.copyOf(other.usesSdkLibraries, @@ -1201,6 +1228,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal long activityInfoToken = proto.start( PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS); proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle()); + proto.write( + ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -1572,6 +1602,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE); } + @Override + public boolean isPersistent() { + return getBoolean(Booleans.PERSISTENT); + } + // Code below generated by codegen v1.0.23. @@ -1714,11 +1749,21 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return mAppMetadataFilePath; } + @DataClass.Generated.Member + public int getTargetSdkVersion() { + return mTargetSdkVersion; + } + + @DataClass.Generated.Member + public @Nullable byte[] getRestrictUpdateHash() { + return mRestrictUpdateHash; + } + @DataClass.Generated( - time = 1694196905013L, + time = 1696979728639L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index da14397b9c92..203e1de61f2f 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -517,12 +517,6 @@ final class ResolveIntentHelper { if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); - // Only if the service query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } - final String instantAppPkgName = computer.getInstantAppPackageName(callingUid); flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 0cac790fd910..8d8acfd421de 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -220,7 +220,8 @@ final class ScanPackageUtils { UserManagerService.getInstance(), usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), - newDomainSetId); + newDomainSetId, parsedPackage.isPersistent(), + parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); } else { // make a deep copy to avoid modifying any existing system state. pkgSetting = new PackageSetting(pkgSetting); @@ -240,7 +241,8 @@ final class ScanPackageUtils { UserManagerService.getInstance(), usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), - parsedPackage.getMimeGroups(), newDomainSetId); + parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(), + parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); } if (createNewPackage && originalPkgSetting != null) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6e3b538c4849..e726d91c30a0 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -368,6 +368,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VALUE = "value"; private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time"; private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title"; + private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name"; private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title"; private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path"; private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path"; @@ -1060,7 +1061,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager, String[] usesSdkLibraries, long[] usesSdkLibrariesVersions, String[] usesStaticLibraries, long[] usesStaticLibrariesVersions, - Set<String> mimeGroupNames, @NonNull UUID domainSetId) { + Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent, + int targetSdkVersion, byte[] restrictUpdatedHash) { final PackageSetting pkgSetting; if (originalPkg != null) { if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " @@ -1080,7 +1082,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions) // Update new package state. .setLastModifiedTime(codePath.lastModified()) - .setDomainSetId(domainSetId); + .setDomainSetId(domainSetId) + .setIsPersistent(isPersistent) + .setTargetSdkVersion(targetSdkVersion) + .setRestrictUpdateHash(restrictUpdatedHash); pkgSetting.setFlags(pkgFlags) .setPrivateFlags(pkgPrivateFlags); } else { @@ -1092,7 +1097,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions, usesStaticLibraries, usesStaticLibrariesVersions, - createMimeGroups(mimeGroupNames), domainSetId); + createMimeGroups(mimeGroupNames), domainSetId) + .setIsPersistent(isPersistent) + .setTargetSdkVersion(targetSdkVersion) + .setRestrictUpdateHash(restrictUpdatedHash); pkgSetting.setLastModifiedTime(codePath.lastModified()); if (sharedUser != null) { pkgSetting.setSharedUserAppId(sharedUser.mAppId); @@ -1206,7 +1214,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile int pkgPrivateFlags, @NonNull UserManagerService userManager, @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions, @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions, - @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId) + @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent, + int targetSdkVersion, byte[] restrictUpdatedHash) throws PackageManagerException { final String pkgName = pkgSetting.getPackageName(); if (sharedUser != null) { @@ -1258,7 +1267,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pkgSetting.setPrimaryCpuAbi(primaryCpuAbi) .setSecondaryCpuAbi(secondaryCpuAbi) .updateMimeGroups(mimeGroupNames) - .setDomainSetId(domainSetId); + .setDomainSetId(domainSetId) + .setIsPersistent(isPersistent) + .setTargetSdkVersion(targetSdkVersion) + .setRestrictUpdateHash(restrictUpdatedHash); // Update SDK library dependencies if needed. if (usesSdkLibraries != null && usesSdkLibrariesVersions != null && usesSdkLibraries.length == usesSdkLibrariesVersions.length) { @@ -2068,6 +2080,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) { String title = parser.getAttributeValue(null, ATTR_ARCHIVE_ACTIVITY_TITLE); + String originalComponentName = + parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME); String iconAttribute = parser.getAttributeValue(null, ATTR_ARCHIVE_ICON_PATH); Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute); @@ -2076,17 +2090,27 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile Path monochromeIconPath = monochromeAttribute == null ? null : Path.of( monochromeAttribute); - if (title == null || iconPath == null) { - Slog.wtf(TAG, - TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s", - TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title, + if (title == null || originalComponentName == null || iconPath == null) { + Slog.wtf( + TAG, + TextUtils.formatSimple( + "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s", + TAG_ARCHIVE_ACTIVITY_INFO, + ATTR_ARCHIVE_ACTIVITY_TITLE, + title, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + originalComponentName, ATTR_ARCHIVE_ICON_PATH, iconPath)); continue; } activityInfos.add( - new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath)); + new ArchiveState.ArchiveActivityInfo( + title, + ComponentName.unflattenFromString(originalComponentName), + iconPath, + monochromeIconPath)); } } return activityInfos; @@ -2458,6 +2482,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) { serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO); serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle()); + serializer.attribute( + null, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -6400,16 +6428,25 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) { + ArrayList<PersistentPreferredActivity> removed = null; PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); Iterator<PersistentPreferredActivity> it = ppir.filterIterator(); boolean changed = false; while (it.hasNext()) { PersistentPreferredActivity ppa = it.next(); if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) { + if (removed == null) { + removed = new ArrayList<>(); + } + removed.add(ppa); + } + } + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + PersistentPreferredActivity ppa = removed.get(i); ppir.removeFilter(ppa); - changed = true; - break; } + changed = true; } if (changed) { onChanged(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0e98158d7210..7331bc141ec2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1335,18 +1335,6 @@ public class UserManagerService extends IUserManager.Stub { && user.profileGroupId == profile.profileGroupId); } - private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode, - boolean useManagedActions) { - Intent intent = new Intent(); - intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions)); - intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode); - intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle()); - intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier()); - intent.addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); - return intent; - } - private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) { return useManagedActions ? enableQuietMode ? @@ -1359,12 +1347,20 @@ public class UserManagerService extends IUserManager.Stub { private void broadcastProfileAvailabilityChanges(UserInfo profileInfo, UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) { - Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode, - useManagedActions); + Intent availabilityIntent = new Intent(); + availabilityIntent.setAction( + getAvailabilityIntentAction(enableQuietMode, useManagedActions)); + availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode); + availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle()); + availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE, + profileInfo.getUserHandle().getIdentifier()); if (profileInfo.isManagedProfile()) { getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers( availabilityIntent, parentHandle, /* requiresPermission= */ true); } + availabilityIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + // TODO(b/302708423): Restrict the apps that can receive these intents in case of a private // profile. final Bundle options = new BroadcastOptions() 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 d16a81267370..61e96ca3dd59 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -379,7 +379,7 @@ public class PackageInfoUtils { ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT) | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD) | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); - if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0 + if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) { ai.enabled = false; } else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { @@ -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/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java index 4916a4a6d72a..1e40d44bd4ca 100644 --- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java +++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java @@ -16,9 +16,11 @@ package com.android.server.pm.pkg; +import android.content.ComponentName; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.util.AnnotationValidations; import com.android.internal.util.DataClass; import java.nio.file.Path; @@ -56,6 +58,10 @@ public class ArchiveState { @NonNull private final String mTitle; + /** The component name of the original activity (pre-archival). */ + @NonNull + private final ComponentName mOriginalComponentName; + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). @@ -96,11 +102,13 @@ public class ArchiveState { @DataClass.Generated.Member public ArchiveActivityInfo( @NonNull String title, + @NonNull ComponentName originalComponentName, @Nullable Path iconBitmap, @Nullable Path monochromeIconBitmap) { this.mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); + this.mOriginalComponentName = originalComponentName; + AnnotationValidations.validate(NonNull.class, null, mTitle); + AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName); this.mIconBitmap = iconBitmap; this.mMonochromeIconBitmap = monochromeIconBitmap; @@ -116,6 +124,14 @@ public class ArchiveState { } /** + * The component name of the original activity (pre-archival). + */ + @DataClass.Generated.Member + public @NonNull ComponentName getOriginalComponentName() { + return mOriginalComponentName; + } + + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). */ @@ -140,6 +156,7 @@ public class ArchiveState { return "ArchiveActivityInfo { " + "title = " + mTitle + ", " + + "originalComponentName = " + mOriginalComponentName + ", " + "iconBitmap = " + mIconBitmap + ", " + "monochromeIconBitmap = " + mMonochromeIconBitmap + " }"; @@ -159,6 +176,7 @@ public class ArchiveState { //noinspection PointlessBooleanExpression return true && java.util.Objects.equals(mTitle, that.mTitle) + && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName) && java.util.Objects.equals(mIconBitmap, that.mIconBitmap) && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap); } @@ -171,6 +189,7 @@ public class ArchiveState { int _hash = 1; _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); + _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName); _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap); _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap); return _hash; @@ -180,7 +199,8 @@ public class ArchiveState { time = 1693590309015L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") + inputSignatures = + "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") @Deprecated private void __metadata() {} @@ -224,11 +244,9 @@ public class ArchiveState { @NonNull List<ArchiveActivityInfo> activityInfos, @NonNull String installerTitle) { this.mActivityInfos = activityInfos; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mActivityInfos); + AnnotationValidations.validate(NonNull.class, null, mActivityInfos); this.mInstallerTitle = installerTitle; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mInstallerTitle); + AnnotationValidations.validate(NonNull.class, null, mInstallerTitle); // onConstructed(); // You can define this method to get a callback } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 3f347e465f81..e7137bbd7969 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -434,4 +434,26 @@ public interface PackageState { */ @Nullable String getApexModuleName(); + + /** + * @see ApplicationInfo#FLAG_PERSISTENT + * @see R.styleable#AndroidManifestApplication_persistent + * @hide + */ + boolean isPersistent(); + + /** + * @see ApplicationInfo#targetSdkVersion + * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion + * @hide + */ + int getTargetSdkVersion(); + + /** + * @see R.styleable#AndroidManifestRestrictUpdate + * @hide + */ + @Immutable.Ignore + @Nullable + byte[] getRestrictUpdateHash(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index 7b07e5b2bb6b..cd3583b814a4 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -16,9 +16,9 @@ package com.android.server.pm.pkg; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS; import android.annotation.NonNull; import android.content.pm.ComponentInfo; @@ -147,7 +147,7 @@ public class PackageUserStateUtils { return true; } - if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) { + if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) { return false; } diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java index 86391c91bea5..153238fa6866 100644 --- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java +++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java @@ -17,7 +17,6 @@ package com.android.server.pm.pkg; import android.annotation.Nullable; -import android.content.pm.Flags; import android.content.pm.SuspendDialogInfo; import android.os.BaseBundle; import android.os.PersistableBundle; @@ -142,8 +141,7 @@ public final class SuspendParams { PersistableBundle readAppExtras = null; PersistableBundle readLauncherExtras = null; - final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false) - && Flags.quarantinedEnabled(); + final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false); final int currentDepth = in.getDepth(); int type; diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp index 1da9dd770ba6..607d435b5410 100644 --- a/services/core/java/com/android/server/power/Android.bp +++ b/services/core/java/com/android/server/power/Android.bp @@ -1,5 +1,5 @@ aconfig_declarations { - name: "power_optimization_flags", + name: "backstage_power_flags", package: "com.android.server.power.optimization", srcs: [ "stats/*.aconfig", @@ -7,6 +7,6 @@ aconfig_declarations { } java_aconfig_library { - name: "power_optimization_flags_lib", - aconfig_declarations: "power_optimization_flags", + name: "backstage_power_flags_lib", + aconfig_declarations: "backstage_power_flags", } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index dfbcbae650cd..4a4214f7af83 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3347,8 +3347,6 @@ public final class PowerManagerService extends SystemService } else { startDreaming = false; } - Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming - + " wakefulness=" + wakefulnessToString(wakefulness)); } // Start dreaming if needed. @@ -3383,23 +3381,19 @@ public final class PowerManagerService extends SystemService if (startDreaming && isDreaming) { mDreamsBatteryLevelDrain = 0; if (wakefulness == WAKEFULNESS_DOZING) { - Slog.i(TAG, "Dozing powerGroup " + groupId); + Slog.i(TAG, "Dozing..."); } else { - Slog.i(TAG, "Dreaming powerGroup " + groupId); + Slog.i(TAG, "Dreaming..."); } } // If preconditions changed, wait for the next iteration to determine // whether the dream should continue (or be restarted). final PowerGroup powerGroup = mPowerGroups.get(groupId); - final int newWakefulness = powerGroup.getWakefulnessLocked(); if (powerGroup.isSandmanSummonedLocked() - || newWakefulness != wakefulness) { + || powerGroup.getWakefulnessLocked() != wakefulness) { return; // wait for next cycle } - Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming - + " wakefulness=" + newWakefulness); - // Determine whether the dream should continue. long now = mClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index 6cc9d0a54607..bc90f5c46130 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -18,15 +18,26 @@ package com.android.server.power.stats; import android.annotation.CurrentTimeMillisLong; import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.os.BatteryConsumer; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.TimeUtils; import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; -import java.io.PrintWriter; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -34,25 +45,75 @@ import java.util.Set; * etc) covering a specific period of power usage history. */ class AggregatedPowerStats { + private static final String TAG = "AggregatedPowerStats"; + private static final int MAX_CLOCK_UPDATES = 100; + private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats"; + private final PowerComponentAggregatedPowerStats[] mPowerComponentStats; - @CurrentTimeMillisLong - private long mStartTime; + static class ClockUpdate { + public long monotonicTime; + @CurrentTimeMillisLong public long currentTime; + } + + private final List<ClockUpdate> mClockUpdates = new ArrayList<>(); @DurationMillisLong private long mDurationMs; - AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) { - this.mPowerComponentStats = powerComponentAggregatedPowerStats; + AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { + List<AggregatedPowerStatsConfig.PowerComponent> configs = + aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs(); + mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()]; + for (int i = 0; i < configs.size(); i++) { + mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i)); + } + } + + private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats( + AggregatedPowerStatsConfig.PowerComponent config) { + switch (config.getPowerComponentId()) { + case BatteryConsumer.POWER_COMPONENT_CPU: + return new CpuAggregatedPowerStats(config); + default: + return new PowerComponentAggregatedPowerStats(config); + } + } + + /** + * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change, + * there may be multiple clock updates in one set of aggregated stats. + * + * @param monotonicTime monotonic time in milliseconds, see + * {@link com.android.internal.os.MonotonicClock} + * @param currentTime current time in milliseconds, see {@link System#currentTimeMillis()} + */ + void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) { + ClockUpdate clockUpdate = new ClockUpdate(); + clockUpdate.monotonicTime = monotonicTime; + clockUpdate.currentTime = currentTime; + if (mClockUpdates.size() < MAX_CLOCK_UPDATES) { + mClockUpdates.add(clockUpdate); + } else { + Slog.i(TAG, "Too many clock updates. Replacing the previous update with " + + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime)); + mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate); + } } - void setStartTime(@CurrentTimeMillisLong long startTime) { - mStartTime = startTime; + /** + * Start time according to {@link com.android.internal.os.MonotonicClock} + */ + long getStartTime() { + if (mClockUpdates.isEmpty()) { + return 0; + } else { + return mClockUpdates.get(0).monotonicTime; + } } - @CurrentTimeMillisLong - public long getStartTime() { - return mStartTime; + List<ClockUpdate> getClockUpdates() { + return mClockUpdates; } void setDuration(long durationMs) { @@ -73,13 +134,14 @@ class AggregatedPowerStats { return null; } - void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) { + void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, + long time) { for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { stats.setState(stateId, state, time); } } - void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state, + void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { stats.setUidState(uid, stateId, state, time); @@ -106,20 +168,90 @@ class AggregatedPowerStats { } void reset() { - mStartTime = 0; + mClockUpdates.clear(); mDurationMs = 0; for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { stats.reset(); } } - void dump(PrintWriter pw) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw); - ipw.print("Start time: "); - ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime)); - ipw.print(" duration: "); - ipw.print(mDurationMs); - ipw.println(); + public void writeXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS); + for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { + stats.writeXml(serializer); + } + serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS); + serializer.flush(); + } + + @NonNull + public static AggregatedPowerStats createFromXml( + TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig) + throws XmlPullParserException, IOException { + AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig); + boolean inElement = false; + boolean skipToEnd = false; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG + && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) { + if (!skipToEnd && eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case XML_TAG_AGGREGATED_POWER_STATS: + inElement = true; + break; + case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT: + if (!inElement) { + break; + } + + int powerComponentId = parser.getAttributeInt(null, + PowerComponentAggregatedPowerStats.XML_ATTR_ID); + for (PowerComponentAggregatedPowerStats powerComponent : + stats.mPowerComponentStats) { + if (powerComponent.powerComponentId == powerComponentId) { + if (!powerComponent.readFromXml(parser)) { + skipToEnd = true; + } + break; + } + } + break; + } + } + eventType = parser.next(); + } + return stats; + } + + void dump(IndentingPrintWriter ipw) { + StringBuilder sb = new StringBuilder(); + long baseTime = 0; + for (int i = 0; i < mClockUpdates.size(); i++) { + ClockUpdate clockUpdate = mClockUpdates.get(i); + sb.setLength(0); + if (i == 0) { + baseTime = clockUpdate.monotonicTime; + sb.append("Start time: ") + .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)) + .append(" (") + .append(baseTime) + .append(") duration: ") + .append(mDurationMs); + ipw.println(sb); + } else { + sb.setLength(0); + sb.append("Clock update: "); + TimeUtils.formatDuration( + clockUpdate.monotonicTime - baseTime, sb, + TimeUtils.HUNDRED_DAY_FIELD_LEN + 3); + sb.append(" ").append( + DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)); + ipw.increaseIndent(); + ipw.println(sb); + ipw.decreaseIndent(); + } + } ipw.println("Device"); ipw.increaseIndent(); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java new file mode 100644 index 000000000000..477c2286abe0 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -0,0 +1,156 @@ +/* + * 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.power.stats; + +import android.annotation.IntDef; +import android.os.BatteryConsumer; + +import com.android.internal.os.MultiStateStats; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration that controls how power stats are aggregated. It determines which state changes + * are to be considered as essential dimensions ("tracked states") for each power component (CPU, + * WiFi, etc). Also, it determines which states are tracked globally and which ones on a per-UID + * basis. + */ +public class AggregatedPowerStatsConfig { + public static final int STATE_POWER = 0; + public static final int STATE_SCREEN = 1; + public static final int STATE_PROCESS_STATE = 2; + + @IntDef({ + STATE_POWER, + STATE_SCREEN, + STATE_PROCESS_STATE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TrackedState { + } + + static final String STATE_NAME_POWER = "pwr"; + static final int POWER_STATE_BATTERY = 0; + static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc. + static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"}; + + static final String STATE_NAME_SCREEN = "scr"; + static final int SCREEN_STATE_ON = 0; + static final int SCREEN_STATE_OTHER = 1; // Off, doze etc + static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"}; + + static final String STATE_NAME_PROCESS_STATE = "ps"; + static final String[] STATE_LABELS_PROCESS_STATE; + + static { + String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT]; + for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) { + procStateLabels[i] = BatteryConsumer.processStateToString(i); + } + STATE_LABELS_PROCESS_STATE = procStateLabels; + } + + /** + * Configuration for a give power component (CPU, WiFi, etc) + */ + public static class PowerComponent { + private final int mPowerComponentId; + private @TrackedState int[] mTrackedDeviceStates; + private @TrackedState int[] mTrackedUidStates; + + PowerComponent(int powerComponentId) { + this.mPowerComponentId = powerComponentId; + } + + /** + * Configures which states should be tracked as separate dimensions for the entire device. + */ + public PowerComponent trackDeviceStates(@TrackedState int... states) { + mTrackedDeviceStates = states; + return this; + } + + /** + * Configures which states should be tracked as separate dimensions on a per-UID basis. + */ + public PowerComponent trackUidStates(@TrackedState int... states) { + mTrackedUidStates = states; + return this; + } + + public int getPowerComponentId() { + return mPowerComponentId; + } + + public MultiStateStats.States[] getDeviceStateConfig() { + return new MultiStateStats.States[]{ + new MultiStateStats.States(STATE_NAME_POWER, + isTracked(mTrackedDeviceStates, STATE_POWER), + STATE_LABELS_POWER), + new MultiStateStats.States(STATE_NAME_SCREEN, + isTracked(mTrackedDeviceStates, STATE_SCREEN), + STATE_LABELS_SCREEN), + }; + } + + public MultiStateStats.States[] getUidStateConfig() { + return new MultiStateStats.States[]{ + new MultiStateStats.States(STATE_NAME_POWER, + isTracked(mTrackedUidStates, STATE_POWER), + AggregatedPowerStatsConfig.STATE_LABELS_POWER), + new MultiStateStats.States(STATE_NAME_SCREEN, + isTracked(mTrackedUidStates, STATE_SCREEN), + AggregatedPowerStatsConfig.STATE_LABELS_SCREEN), + new MultiStateStats.States(STATE_NAME_PROCESS_STATE, + isTracked(mTrackedUidStates, STATE_PROCESS_STATE), + AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE), + }; + } + + private boolean isTracked(int[] trackedStates, int state) { + if (trackedStates == null) { + return false; + } + + for (int trackedState : trackedStates) { + if (trackedState == state) { + return true; + } + } + return false; + } + } + + private final List<PowerComponent> mPowerComponents = new ArrayList<>(); + + /** + * Creates a configuration for the specified power component, which may be one of the + * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or + * a custom power component. + */ + public PowerComponent trackPowerComponent(int powerComponentId) { + PowerComponent builder = new PowerComponent(powerComponentId); + mPowerComponents.add(builder); + return builder; + } + + public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() { + return mPowerComponents; + } +} diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java new file mode 100644 index 000000000000..7ba433017413 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java @@ -0,0 +1,48 @@ +/* + * 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.power.stats; + +import android.util.IndentingPrintWriter; + +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; + +class AggregatedPowerStatsSection extends PowerStatsSpan.Section { + public static final String TYPE = "aggregated-power-stats"; + + private final AggregatedPowerStats mAggregatedPowerStats; + + AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) { + super(TYPE); + mAggregatedPowerStats = aggregatedPowerStats; + } + + public AggregatedPowerStats getAggregatedPowerStats() { + return mAggregatedPowerStats; + } + + @Override + void write(TypedXmlSerializer serializer) throws IOException { + mAggregatedPowerStats.writeXml(serializer); + } + + @Override + public void dump(IndentingPrintWriter ipw) { + mAggregatedPowerStats.dump(ipw); + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 613f18982b86..a9c2bc28b7a0 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -124,6 +124,7 @@ import com.android.internal.os.KernelMemoryBandwidthStats; import com.android.internal.os.KernelSingleUidTimeReader; import com.android.internal.os.LongArrayMultiStateCounter; import com.android.internal.os.LongMultiStateCounter; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; import com.android.internal.os.RailStats; @@ -283,7 +284,6 @@ public class BatteryStatsImpl extends BatteryStats { private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>(); private int[] mCpuPowerBracketMap; private final CpuPowerStatsCollector mCpuPowerStatsCollector; - private final PowerStatsAggregator mPowerStatsAggregator; public LongSparseArray<SamplingTimer> getKernelMemoryStats() { return mKernelMemoryStats; @@ -356,6 +356,11 @@ public class BatteryStatsImpl extends BatteryStats { protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>(); @NonNull + public BatteryStatsHistory getHistory() { + return mHistory; + } + + @NonNull BatteryStatsHistory copyHistory() { return mHistory.copy(); } @@ -1718,14 +1723,7 @@ public class BatteryStatsImpl extends BatteryStats { return mMaxLearnedBatteryCapacityUah; } - public BatteryStatsImpl() { - this(Clock.SYSTEM_CLOCK); - } - - public BatteryStatsImpl(Clock clock) { - this(clock, null); - } - + @VisibleForTesting public BatteryStatsImpl(Clock clock, File historyDirectory) { init(clock); mBatteryStatsConfig = new BatteryStatsConfig.Builder().build(); @@ -1737,18 +1735,19 @@ public class BatteryStatsImpl extends BatteryStats { mCheckinFile = null; mStatsFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, + new MonotonicClock(0, mClock)); } else { mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin")); mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin")); mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, + new MonotonicClock(0, mClock)); } mPlatformIdleStateCallback = null; mEnergyConsumerRetriever = null; mUserInfoProvider = null; mCpuPowerStatsCollector = null; - mPowerStatsAggregator = null; } private void init(Clock clock) { @@ -10906,20 +10905,12 @@ public class BatteryStatsImpl extends BatteryStats { return mTmpCpuTimeInFreq; } - public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir, + public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, + @NonNull MonotonicClock monotonicClock, @Nullable File systemDir, @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies) { - this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider, - powerProfile, cpuScalingPolicies); - } - - private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, - @Nullable File systemDir, @NonNull Handler handler, - @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, - @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, - @NonNull CpuScalingPolicies cpuScalingPolicies) { init(clock); mBatteryStatsConfig = config; @@ -10936,31 +10927,19 @@ public class BatteryStatsImpl extends BatteryStats { mCheckinFile = null; mDailyFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); } else { mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin")); mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin")); mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml")); mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); } mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile, mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); - PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory); - builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) - .trackDeviceStates( - PowerStatsAggregator.STATE_POWER, - PowerStatsAggregator.STATE_SCREEN) - .trackUidStates( - PowerStatsAggregator.STATE_POWER, - PowerStatsAggregator.STATE_SCREEN, - PowerStatsAggregator.STATE_PROCESS_STATE); - - mPowerStatsAggregator = builder.build(); - mStartCount++; initTimersAndCounters(); mOnBattery = mOnBatteryInternal = false; @@ -15702,18 +15681,21 @@ public class BatteryStatsImpl extends BatteryStats { } /** - * Grabs one sample of PowerStats and prints it. + * Schedules an immediate (but asynchronous) collection of PowerStats samples. + * Callers will need to wait for the collection to complete on the handler thread. */ - public void dumpStatsSample(PrintWriter pw) { - mCpuPowerStatsCollector.collectAndDump(pw); + public void schedulePowerStatsSampleCollection() { + if (mCpuPowerStatsCollector == null) { + return; + } + mCpuPowerStatsCollector.forceSchedule(); } /** - * Aggregates power stats between the specified times and prints them. + * Grabs one sample of PowerStats and prints it. */ - public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) { - mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs, - stats-> stats.dump(pw)); + public void dumpStatsSample(PrintWriter pw) { + mCpuPowerStatsCollector.collectAndDump(pw); } private final Runnable mWriteAsyncRunnable = () -> { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index f6fa9f244252..851a3f7bdaba 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -46,7 +46,7 @@ public class BatteryUsageStatsProvider { private static final String TAG = "BatteryUsageStatsProv"; private final Context mContext; private final BatteryStats mStats; - private final BatteryUsageStatsStore mBatteryUsageStatsStore; + private final PowerStatsStore mPowerStatsStore; private final PowerProfile mPowerProfile; private final CpuScalingPolicies mCpuScalingPolicies; private final Object mLock = new Object(); @@ -58,10 +58,10 @@ public class BatteryUsageStatsProvider { @VisibleForTesting public BatteryUsageStatsProvider(Context context, BatteryStats stats, - BatteryUsageStatsStore batteryUsageStatsStore) { + PowerStatsStore powerStatsStore) { mContext = context; mStats = stats; - mBatteryUsageStatsStore = batteryUsageStatsStore; + mPowerStatsStore = powerStatsStore; mPowerProfile = stats instanceof BatteryStatsImpl ? ((BatteryStatsImpl) stats).getPowerProfile() : new PowerProfile(context); @@ -314,20 +314,52 @@ public class BatteryUsageStatsProvider { final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( customEnergyConsumerNames, includePowerModels, includeProcessStateData, minConsumedPowerThreshold); - if (mBatteryUsageStatsStore == null) { - Log.e(TAG, "BatteryUsageStatsStore is unavailable"); + if (mPowerStatsStore == null) { + Log.e(TAG, "PowerStatsStore is unavailable"); return builder.build(); } - final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); - for (long timestamp : timestamps) { - if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) { - final BatteryUsageStats snapshot = - mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp); - if (snapshot == null) { - continue; - } + List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents(); + for (PowerStatsSpan.Metadata spanMetadata : toc) { + if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) { + continue; + } + + // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the + // session end time. + // + // The following algorithm is correct when there is only one time frame in the span. + // When the wall-clock time is adjusted in the middle of an stats span, + // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm + // only covers some situations, but not others. When using the resulting data for + // analysis, we should always pay attention to the full set of included timeframes. + // TODO(b/298459065): switch to monotonic clock + long minTime = Long.MAX_VALUE; + long maxTime = 0; + for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) { + long spanEndTime = timeFrame.startTime + timeFrame.duration; + minTime = Math.min(minTime, spanEndTime); + maxTime = Math.max(maxTime, spanEndTime); + } + + // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*, + // while the "to" timestamp is *inclusive*. + boolean isInRange = + (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp()) + && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp()); + if (!isInRange) { + continue; + } + + PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan( + spanMetadata.getId(), BatteryUsageStatsSection.TYPE); + if (powerStatsSpan == null) { + continue; + } + for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) { + BatteryUsageStats snapshot = + ((BatteryUsageStatsSection) section).getBatteryUsageStats(); if (!Arrays.equals(snapshot.getCustomPowerComponentNames(), customEnergyConsumerNames)) { Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different " diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java new file mode 100644 index 000000000000..b95faac7c111 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java @@ -0,0 +1,49 @@ +/* + * 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.power.stats; + +import android.os.BatteryUsageStats; +import android.util.IndentingPrintWriter; + +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; + +class BatteryUsageStatsSection extends PowerStatsSpan.Section { + public static final String TYPE = "battery-usage-stats"; + + private final BatteryUsageStats mBatteryUsageStats; + + BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) { + super(TYPE); + mBatteryUsageStats = batteryUsageStats; + } + + public BatteryUsageStats getBatteryUsageStats() { + return mBatteryUsageStats; + } + + @Override + void write(TypedXmlSerializer serializer) throws IOException { + mBatteryUsageStats.writeXml(serializer); + } + + @Override + public void dump(IndentingPrintWriter ipw) { + mBatteryUsageStats.dump(ipw, ""); + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java deleted file mode 100644 index 0d7a1406f751..000000000000 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power.stats; - -import android.annotation.Nullable; -import android.content.Context; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.util.AtomicFile; -import android.util.Log; -import android.util.LongArray; -import android.util.Slog; -import android.util.Xml; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.charset.StandardCharsets; -import java.nio.file.StandardOpenOption; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.TreeMap; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A storage mechanism for BatteryUsageStats snapshots. - */ -public class BatteryUsageStatsStore { - private static final String TAG = "BatteryUsageStatsStore"; - - private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of( - new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0) - .includePowerModels() - .includeProcessStateData() - .build()); - private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; - private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; - private static final String DIR_LOCK_FILENAME = ".lock"; - private static final String CONFIG_FILENAME = "config"; - private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = - "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; - private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024; - - private final Context mContext; - private final BatteryStatsImpl mBatteryStats; - private boolean mSystemReady; - private final File mStoreDir; - private final File mLockFile; - private final ReentrantLock mFileLock = new ReentrantLock(); - private FileLock mJvmLock; - private final AtomicFile mConfigFile; - private final long mMaxStorageBytes; - private final Handler mHandler; - private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; - - public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir, - Handler handler) { - this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); - } - - @VisibleForTesting - public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir, - Handler handler, long maxStorageBytes) { - mContext = context; - mBatteryStats = batteryStats; - mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR); - mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); - mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME)); - mHandler = handler; - mMaxStorageBytes = maxStorageBytes; - mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats); - } - - /** - * Notifies BatteryUsageStatsStore that the system server is ready. - */ - public void onSystemReady() { - mSystemReady = true; - } - - private void prepareForBatteryStatsReset(int resetReason) { - if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) { - return; - } - - final List<BatteryUsageStats> stats = - mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY); - if (stats.isEmpty()) { - Slog.wtf(TAG, "No battery usage stats generated"); - return; - } - - mHandler.post(() -> storeBatteryUsageStats(stats.get(0))); - } - - private void storeBatteryUsageStats(BatteryUsageStats stats) { - lockSnapshotDirectory(); - try { - if (!mStoreDir.exists()) { - if (!mStoreDir.mkdirs()) { - Slog.e(TAG, "Could not create a directory for battery usage stats snapshots"); - return; - } - } - File file = makeSnapshotFilename(stats.getStatsEndTimestamp()); - try { - writeXmlFileLocked(stats, file); - } catch (Exception e) { - Slog.e(TAG, "Cannot save battery usage stats", e); - } - - removeOldSnapshotsLocked(); - } finally { - unlockSnapshotDirectory(); - } - } - - /** - * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds - * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}. - */ - public long[] listBatteryUsageStatsTimestamps() { - LongArray timestamps = new LongArray(100); - lockSnapshotDirectory(); - try { - for (File file : mStoreDir.listFiles()) { - String fileName = file.getName(); - if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) { - try { - String fileNameWithoutExtension = fileName.substring(0, - fileName.length() - SNAPSHOT_FILE_EXTENSION.length()); - timestamps.add(Long.parseLong(fileNameWithoutExtension)); - } catch (NumberFormatException e) { - Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: " - + fileName); - } - } - } - } finally { - unlockSnapshotDirectory(); - } - return timestamps.toArray(); - } - - /** - * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot - * does not exist. - */ - @Nullable - public BatteryUsageStats loadBatteryUsageStats(long timestamp) { - lockSnapshotDirectory(); - try { - File file = makeSnapshotFilename(timestamp); - try { - return readXmlFileLocked(file); - } catch (Exception e) { - Slog.e(TAG, "Cannot read battery usage stats", e); - } - } finally { - unlockSnapshotDirectory(); - } - return null; - } - - /** - * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull - * in persistent file. - */ - public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { - Properties props = new Properties(); - lockSnapshotDirectory(); - try { - try (InputStream in = mConfigFile.openRead()) { - props.load(in); - } catch (IOException e) { - Slog.e(TAG, "Cannot load config file " + mConfigFile, e); - } - props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, - String.valueOf(timestamp)); - FileOutputStream out = null; - try { - out = mConfigFile.startWrite(); - props.store(out, "Statsd atom pull timestamps"); - mConfigFile.finishWrite(out); - } catch (IOException e) { - mConfigFile.failWrite(out); - Slog.e(TAG, "Cannot save config file " + mConfigFile, e); - } - } finally { - unlockSnapshotDirectory(); - } - } - - /** - * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET - * statsd atom pull. - */ - public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { - Properties props = new Properties(); - lockSnapshotDirectory(); - try { - try (InputStream in = mConfigFile.openRead()) { - props.load(in); - } catch (IOException e) { - Slog.e(TAG, "Cannot load config file " + mConfigFile, e); - } - } finally { - unlockSnapshotDirectory(); - } - return Long.parseLong( - props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); - } - - private void lockSnapshotDirectory() { - mFileLock.lock(); - - // Lock the directory from access by other JVMs - try { - mLockFile.getParentFile().mkdirs(); - mLockFile.createNewFile(); - mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); - } catch (IOException e) { - Log.e(TAG, "Cannot lock snapshot directory", e); - } - } - - private void unlockSnapshotDirectory() { - try { - mJvmLock.close(); - } catch (IOException e) { - Log.e(TAG, "Cannot unlock snapshot directory", e); - } finally { - mFileLock.unlock(); - } - } - - /** - * Creates a file name by formatting the timestamp as 19-digit zero-padded number. - * This ensures that sorted directory list follows the chronological order. - */ - private File makeSnapshotFilename(long statsEndTimestamp) { - return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp) - + SNAPSHOT_FILE_EXTENSION); - } - - private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException { - try (OutputStream out = new FileOutputStream(file)) { - TypedXmlSerializer serializer = Xml.newBinarySerializer(); - serializer.setOutput(out, StandardCharsets.UTF_8.name()); - serializer.startDocument(null, true); - stats.writeXml(serializer); - serializer.endDocument(); - } - } - - private BatteryUsageStats readXmlFileLocked(File file) - throws IOException, XmlPullParserException { - try (InputStream in = new FileInputStream(file)) { - TypedXmlPullParser parser = Xml.newBinaryPullParser(); - parser.setInput(in, StandardCharsets.UTF_8.name()); - return BatteryUsageStats.createFromXml(parser); - } - } - - private void removeOldSnapshotsLocked() { - // Read the directory list into a _sorted_ map. The alphanumeric ordering - // corresponds to the historical order of snapshots because the file names - // are timestamps zero-padded to the same length. - long totalSize = 0; - TreeMap<File, Long> mFileSizes = new TreeMap<>(); - for (File file : mStoreDir.listFiles()) { - final long fileSize = file.length(); - totalSize += fileSize; - if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { - mFileSizes.put(file, fileSize); - } - } - - while (totalSize > mMaxStorageBytes) { - final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); - if (entry == null) { - break; - } - - File file = entry.getKey(); - if (!file.delete()) { - Slog.e(TAG, "Cannot delete battery usage stats " + file); - } - totalSize -= entry.getValue(); - mFileSizes.remove(file); - } - } - - public void removeAllSnapshots() { - lockSnapshotDirectory(); - try { - for (File file : mStoreDir.listFiles()) { - if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { - if (!file.delete()) { - Slog.e(TAG, "Cannot delete battery usage stats " + file); - } - } - } - } finally { - unlockSnapshotDirectory(); - } - } -} diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java index 5b3fe064d79a..fbf692800e52 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java @@ -16,14 +16,8 @@ package com.android.server.power.stats; -import android.os.BatteryConsumer; - -import com.android.internal.os.MultiStateStats; - class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - - CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates, - MultiStateStats.States[] uidStates) { - super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates); + CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { + super(config); } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 686268fea22b..05c0a13642b4 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -21,7 +21,13 @@ import android.util.SparseArray; import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.util.Collection; /** @@ -31,6 +37,12 @@ import java.util.Collection; * as part of the {@link PowerStats.Descriptor}. */ class PowerComponentAggregatedPowerStats { + static final String XML_TAG_POWER_COMPONENT = "power_component"; + static final String XML_ATTR_ID = "id"; + private static final String XML_TAG_DEVICE_STATS = "device-stats"; + private static final String XML_TAG_UID_STATS = "uid-stats"; + private static final String XML_ATTR_UID = "uid"; + public final int powerComponentId; private final MultiStateStats.States[] mDeviceStateConfig; private final MultiStateStats.States[] mUidStateConfig; @@ -49,22 +61,24 @@ class PowerComponentAggregatedPowerStats { public MultiStateStats stats; } - PowerComponentAggregatedPowerStats(int powerComponentId, - MultiStateStats.States[] deviceStates, - MultiStateStats.States[] uidStates) { - this.powerComponentId = powerComponentId; - mDeviceStateConfig = deviceStates; - mUidStateConfig = uidStates; + PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { + this.powerComponentId = config.getPowerComponentId(); + mDeviceStateConfig = config.getDeviceStateConfig(); + mUidStateConfig = config.getUidStateConfig(); mDeviceStates = new int[mDeviceStateConfig.length]; mDeviceStateTimestamps = new long[mDeviceStateConfig.length]; } - void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) { + public PowerStats.Descriptor getPowerStatsDescriptor() { + return mPowerStatsDescriptor; + } + + void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { mDeviceStates[stateId] = state; mDeviceStateTimestamps[stateId] = time; if (mDeviceStateConfig[stateId].isTracked()) { - if (mDeviceStats != null || createDeviceStats()) { + if (mDeviceStats != null) { mDeviceStats.setState(stateId, state, time); } } @@ -72,14 +86,14 @@ class PowerComponentAggregatedPowerStats { if (mUidStateConfig[stateId].isTracked()) { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); - if (uidStats.stats != null || createUidStats(uidStats)) { + if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); } } } } - void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state, + void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { if (!mUidStateConfig[stateId].isTracked()) { return; @@ -89,7 +103,7 @@ class PowerComponentAggregatedPowerStats { uidStats.states[stateId] = state; uidStats.stateTimestampMs[stateId] = time; - if (uidStats.stats != null || createUidStats(uidStats)) { + if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); } } @@ -102,13 +116,6 @@ class PowerComponentAggregatedPowerStats { mPowerStatsDescriptor = powerStats.descriptor; if (mDeviceStats == null) { - if (mStatsFactory == null) { - mStatsFactory = new MultiStateStats.Factory( - mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig); - mUidStatsFactory = new MultiStateStats.Factory( - mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig); - } - createDeviceStats(); } @@ -183,7 +190,11 @@ class PowerComponentAggregatedPowerStats { private boolean createDeviceStats() { if (mStatsFactory == null) { - return false; + if (mPowerStatsDescriptor == null) { + return false; + } + mStatsFactory = new MultiStateStats.Factory( + mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig); } mDeviceStats = mStatsFactory.create(); @@ -196,7 +207,11 @@ class PowerComponentAggregatedPowerStats { private boolean createUidStats(UidStats uidStats) { if (mUidStatsFactory == null) { - return false; + if (mPowerStatsDescriptor == null) { + return false; + } + mUidStatsFactory = new MultiStateStats.Factory( + mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig); } uidStats.stats = mUidStatsFactory.create(); @@ -211,6 +226,74 @@ class PowerComponentAggregatedPowerStats { return true; } + public void writeXml(TypedXmlSerializer serializer) throws IOException { + // No stats aggregated - can skip writing XML altogether + if (mPowerStatsDescriptor == null) { + return; + } + + serializer.startTag(null, XML_TAG_POWER_COMPONENT); + serializer.attributeInt(null, XML_ATTR_ID, powerComponentId); + mPowerStatsDescriptor.writeXml(serializer); + + if (mDeviceStats != null) { + serializer.startTag(null, XML_TAG_DEVICE_STATS); + mDeviceStats.writeXml(serializer); + serializer.endTag(null, XML_TAG_DEVICE_STATS); + } + + for (int i = mUidStats.size() - 1; i >= 0; i--) { + int uid = mUidStats.keyAt(i); + UidStats uidStats = mUidStats.valueAt(i); + if (uidStats.stats != null) { + serializer.startTag(null, XML_TAG_UID_STATS); + serializer.attributeInt(null, XML_ATTR_UID, uid); + uidStats.stats.writeXml(serializer); + serializer.endTag(null, XML_TAG_UID_STATS); + } + } + + serializer.endTag(null, XML_TAG_POWER_COMPONENT); + serializer.flush(); + } + + public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, + IOException { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case PowerStats.Descriptor.XML_TAG_DESCRIPTOR: + mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser); + if (mPowerStatsDescriptor == null) { + return false; + } + break; + case XML_TAG_DEVICE_STATS: + if (mDeviceStats == null) { + createDeviceStats(); + } + if (!mDeviceStats.readFromXml(parser)) { + return false; + } + break; + case XML_TAG_UID_STATS: + int uid = parser.getAttributeInt(null, XML_ATTR_UID); + UidStats uidStats = getUidStats(uid); + if (uidStats.stats == null) { + createUidStats(uidStats); + } + if (!uidStats.stats.readFromXml(parser)) { + return false; + } + break; + } + } + eventType = parser.next(); + } + return true; + } + void dumpDevice(IndentingPrintWriter ipw) { if (mDeviceStats != null) { ipw.println(mPowerStatsDescriptor.name); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index 6a1c1da93163..f374fb77cae8 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -15,59 +15,26 @@ */ package com.android.server.power.stats; -import android.annotation.IntDef; -import android.os.BatteryConsumer; import android.os.BatteryStats; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; -import com.android.internal.os.MultiStateStats; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; -class PowerStatsAggregator { - public static final int STATE_POWER = 0; - public static final int STATE_SCREEN = 1; - public static final int STATE_PROCESS_STATE = 2; - - @IntDef({ - STATE_POWER, - STATE_SCREEN, - STATE_PROCESS_STATE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TrackedState { - } - - static final int POWER_STATE_BATTERY = 0; - static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc. - static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"}; - - static final int SCREEN_STATE_ON = 0; - static final int SCREEN_STATE_OTHER = 1; // Off, doze etc - static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"}; - - static final String[] STATE_LABELS_PROCESS_STATE; - - static { - String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT]; - for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) { - procStateLabels[i] = BatteryConsumer.processStateToString(i); - } - STATE_LABELS_PROCESS_STATE = procStateLabels; - } - - private final BatteryStatsHistory mHistory; +/** + * Power stats aggregator. It reads through portions of battery stats history, finds + * relevant items (state changes, power stats etc) and produces one or more + * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history. + */ +public class PowerStatsAggregator { private final AggregatedPowerStats mStats; + private final BatteryStatsHistory mHistory; - private PowerStatsAggregator(BatteryStatsHistory history, - AggregatedPowerStats aggregatedPowerStats) { + public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, + BatteryStatsHistory history) { + mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig); mHistory = history; - mStats = aggregatedPowerStats; } /** @@ -82,12 +49,10 @@ class PowerStatsAggregator { * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume * the stats in the <code>accept</code> method and never cache it. */ - void aggregateBatteryStats(long startTimeMs, long endTimeMs, + public void aggregatePowerStats(long startTimeMs, long endTimeMs, Consumer<AggregatedPowerStats> consumer) { - mStats.reset(); - - int currentBatteryState = POWER_STATE_BATTERY; - int currentScreenState = SCREEN_STATE_OTHER; + int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; + int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; long baseTime = -1; long lastTime = 0; try (BatteryStatsHistoryIterator iterator = @@ -96,131 +61,60 @@ class PowerStatsAggregator { BatteryStats.HistoryItem item = iterator.next(); if (baseTime < 0) { - mStats.setStartTime(item.currentTime); + mStats.addClockUpdate(item.time, item.currentTime); baseTime = item.time; + } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME + || item.cmd == BatteryStats.HistoryItem.CMD_RESET) { + mStats.addClockUpdate(item.time, item.currentTime); } lastTime = item.time; int batteryState = (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0 - ? POWER_STATE_OTHER : POWER_STATE_BATTERY; + ? AggregatedPowerStatsConfig.POWER_STATE_OTHER + : AggregatedPowerStatsConfig.POWER_STATE_BATTERY; if (batteryState != currentBatteryState) { - mStats.setDeviceState(STATE_POWER, batteryState, item.time); + mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState, + item.time); currentBatteryState = batteryState; } int screenState = (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0 - ? SCREEN_STATE_ON : SCREEN_STATE_OTHER; + ? AggregatedPowerStatsConfig.SCREEN_STATE_ON + : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; if (screenState != currentScreenState) { - mStats.setDeviceState(STATE_SCREEN, screenState, item.time); + mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState, + item.time); currentScreenState = screenState; } if (item.processStateChange != null) { - mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE, + mStats.setUidState(item.processStateChange.uid, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE, item.processStateChange.processState, item.time); } if (item.powerStats != null) { if (!mStats.isCompatible(item.powerStats)) { - mStats.setDuration(lastTime - baseTime); - consumer.accept(mStats); + if (lastTime > baseTime) { + mStats.setDuration(lastTime - baseTime); + consumer.accept(mStats); + } mStats.reset(); - mStats.setStartTime(item.currentTime); + mStats.addClockUpdate(item.time, item.currentTime); baseTime = lastTime = item.time; } mStats.addPowerStats(item.powerStats, item.time); } } } - mStats.setDuration(lastTime - baseTime); - consumer.accept(mStats); - } - - static class Builder { - static class PowerComponentAggregateStatsBuilder { - private final int mPowerComponentId; - private @TrackedState int[] mTrackedDeviceStates; - private @TrackedState int[] mTrackedUidStates; - - PowerComponentAggregateStatsBuilder(int powerComponentId) { - this.mPowerComponentId = powerComponentId; - } - - public PowerComponentAggregateStatsBuilder trackDeviceStates( - @TrackedState int... states) { - mTrackedDeviceStates = states; - return this; - } - - public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) { - mTrackedUidStates = states; - return this; - } - - private PowerComponentAggregatedPowerStats build() { - MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{ - new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER), - PowerStatsAggregator.STATE_LABELS_POWER), - new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN), - PowerStatsAggregator.STATE_LABELS_SCREEN), - }; - - MultiStateStats.States[] uidStates = new MultiStateStats.States[]{ - new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER), - PowerStatsAggregator.STATE_LABELS_POWER), - new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN), - PowerStatsAggregator.STATE_LABELS_SCREEN), - new MultiStateStats.States( - isTracked(mTrackedUidStates, STATE_PROCESS_STATE), - PowerStatsAggregator.STATE_LABELS_PROCESS_STATE), - }; - - switch (mPowerComponentId) { - case BatteryConsumer.POWER_COMPONENT_CPU: - return new CpuAggregatedPowerStats(deviceStates, uidStates); - default: - return new PowerComponentAggregatedPowerStats(mPowerComponentId, - deviceStates, uidStates); - } - } - - private boolean isTracked(int[] trackedStates, int state) { - if (trackedStates == null) { - return false; - } - - for (int trackedState : trackedStates) { - if (trackedState == state) { - return true; - } - } - return false; - } - } - - private final BatteryStatsHistory mHistory; - private final List<PowerComponentAggregateStatsBuilder> mPowerComponents = - new ArrayList<>(); - - Builder(BatteryStatsHistory history) { - mHistory = history; - } - - PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) { - PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder( - powerComponentId); - mPowerComponents.add(builder); - return builder; + if (lastTime > baseTime) { + mStats.setDuration(lastTime - baseTime); + consumer.accept(mStats); } - PowerStatsAggregator build() { - return new PowerStatsAggregator(mHistory, new AggregatedPowerStats( - mPowerComponents.stream() - .map(PowerComponentAggregateStatsBuilder::build) - .toArray(PowerComponentAggregatedPowerStats[]::new))); - } + mStats.reset(); // to free up memory } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java new file mode 100644 index 000000000000..58619c70e0af --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.DurationMillisLong; +import android.app.AlarmManager; +import android.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.ConditionVariable; +import android.os.Handler; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.Clock; +import com.android.internal.os.MonotonicClock; + +import java.io.PrintWriter; +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +/** + * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in + * {@link PowerStatsStore}. + */ +public class PowerStatsScheduler { + private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); + private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); + + private final Context mContext; + private boolean mEnablePeriodicPowerStatsCollection; + @DurationMillisLong + private final long mAggregatedPowerStatsSpanDuration; + @DurationMillisLong + private final long mPowerStatsAggregationPeriod; + private final PowerStatsStore mPowerStatsStore; + private final Clock mClock; + private final MonotonicClock mMonotonicClock; + private final Handler mHandler; + private final BatteryStatsImpl mBatteryStats; + private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private final PowerStatsAggregator mPowerStatsAggregator; + private long mLastSavedSpanEndMonotonicTime; + + public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator, + @DurationMillisLong long aggregatedPowerStatsSpanDuration, + @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, + Clock clock, MonotonicClock monotonicClock, Handler handler, + BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) { + mContext = context; + mPowerStatsAggregator = powerStatsAggregator; + mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; + mPowerStatsAggregationPeriod = powerStatsAggregationPeriod; + mPowerStatsStore = powerStatsStore; + mClock = clock; + mMonotonicClock = monotonicClock; + mHandler = handler; + mBatteryStats = batteryStats; + mBatteryUsageStatsProvider = batteryUsageStatsProvider; + } + + /** + * Kicks off the scheduling of power stats aggregation spans. + */ + public void start(boolean enablePeriodicPowerStatsCollection) { + mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset); + mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection; + if (mEnablePeriodicPowerStatsCollection) { + scheduleNextPowerStatsAggregation(); + } + } + + private void scheduleNextPowerStatsAggregation() { + AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + alarmManager.set(AlarmManager.ELAPSED_REALTIME, + mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats", + () -> { + schedulePowerStatsAggregation(); + mHandler.post(this::scheduleNextPowerStatsAggregation); + }, mHandler); + } + + /** + * Initiate an asynchronous process of aggregation of power stats. + */ + @VisibleForTesting + public void schedulePowerStatsAggregation() { + // Catch up the power stats collectors + mBatteryStats.schedulePowerStatsSampleCollection(); + mHandler.post(this::aggregateAndStorePowerStats); + } + + private void aggregateAndStorePowerStats() { + long currentTimeMillis = mClock.currentTimeMillis(); + long currentMonotonicTime = mMonotonicClock.monotonicTime(); + long startTime = getLastSavedSpanEndMonotonicTime(); + long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration, + mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis); + while (endTimeMs <= currentMonotonicTime) { + mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs, + stats -> { + storeAggregatedPowerStats(stats); + mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration(); + }); + + startTime = endTimeMs; + endTimeMs += mAggregatedPowerStatsSpanDuration; + } + } + + /** + * Performs a power stats aggregation pass and then dumps all stored aggregated power stats + * spans followed by the remainder that has not been stored yet. + */ + public void aggregateAndDumpPowerStats(PrintWriter pw) { + if (mHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException("Should not be executed on the bg handler thread."); + } + + schedulePowerStatsAggregation(); + + // Wait for the aggregation process to finish storing aggregated stats spans in the store. + awaitCompletion(); + + IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + mHandler.post(() -> { + mPowerStatsStore.dump(ipw); + // Aggregate the remainder of power stats and dump the results without storing them yet. + long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime(); + mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0, + stats -> { + // Create a PowerStatsSpan for consistency of the textual output + PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats); + if (span != null) { + span.dump(ipw); + } + }); + }); + + awaitCompletion(); + } + + /** + * Align the supplied time to the wall clock, for aesthetic purposes. For example, if + * the schedule is configured with a 15-min interval, the captured aggregated stats will + * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current + * time is used for the alignment, so if the wall clock changed during an aggregation span, + * or if the device was off (which stops the monotonic clock), the alignment may be + * temporarily broken. + */ + @VisibleForTesting + public static long alignToWallClock(long targetMonotonicTime, long interval, + long currentMonotonicTime, long currentTimeMillis) { + + // Estimate the wall clock time for the requested targetMonotonicTime + long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime); + + if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) { + // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc + + // First, round up to the next whole minute + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + // Now set the minute to a multiple of the requested interval + int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS); + cal.set(Calendar.MINUTE, + ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes) + * intervalInMinutes); + + long adjustment = cal.getTimeInMillis() - targetWallClockTime; + return targetMonotonicTime + adjustment; + } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) { + // If the interval is a divisor of a day, e.g. 2h, 3h, etc + + // First, round up to the next whole hour + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + // Now set the hour of day to a multiple of the requested interval + int intervalInHours = (int) (interval / HOUR_IN_MILLIS); + cal.set(Calendar.HOUR_OF_DAY, + ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours) + * intervalInHours); + + long adjustment = cal.getTimeInMillis() - targetWallClockTime; + return targetMonotonicTime + adjustment; + } + + return targetMonotonicTime; + } + + private long getLastSavedSpanEndMonotonicTime() { + if (mLastSavedSpanEndMonotonicTime != 0) { + return mLastSavedSpanEndMonotonicTime; + } + + for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) { + if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) { + for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) { + long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration; + if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) { + mLastSavedSpanEndMonotonicTime = endMonotonicTime; + } + } + } + } + return mLastSavedSpanEndMonotonicTime; + } + + private void storeAggregatedPowerStats(AggregatedPowerStats stats) { + mPowerStatsStore.storeAggregatedPowerStats(stats); + } + + private void storeBatteryUsageStatsOnReset(int resetReason) { + if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { + return; + } + + final BatteryUsageStats batteryUsageStats = + mBatteryUsageStatsProvider.getBatteryUsageStats( + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .includeProcessStateData() + .build()); + + // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end + // Once that change is made, we will be able to use the BatteryUsageStats' monotonic + // start time + long monotonicStartTime = + mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); + mHandler.post(() -> + mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); + } + + private void awaitCompletion() { + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java new file mode 100644 index 000000000000..3b260ca0c3b9 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java @@ -0,0 +1,433 @@ +/* + * 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.power.stats; + +import android.annotation.CurrentTimeMillisLong; +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import com.google.android.collect.Sets; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +/** + * Contains power stats of various kinds, aggregated over a time span. + */ +public class PowerStatsSpan { + private static final String TAG = "PowerStatsStore"; + + /** + * Increment VERSION when the XML format of the store changes. Also, update + * {@link #isCompatibleXmlFormat} to return true for all legacy versions + * that are compatible with the new one. + */ + private static final int VERSION = 1; + + private static final String XML_TAG_METADATA = "metadata"; + private static final String XML_ATTR_ID = "id"; + private static final String XML_ATTR_VERSION = "version"; + private static final String XML_TAG_TIMEFRAME = "timeframe"; + private static final String XML_ATTR_MONOTONIC = "monotonic"; + private static final String XML_ATTR_START_TIME = "start"; + private static final String XML_ATTR_DURATION = "duration"; + private static final String XML_TAG_SECTION = "section"; + private static final String XML_ATTR_SECTION_TYPE = "type"; + + private static final DateTimeFormatter DATE_FORMAT = + DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + + static class TimeFrame { + public final long startMonotonicTime; + @CurrentTimeMillisLong + public final long startTime; + @DurationMillisLong + public final long duration; + + TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime, + @DurationMillisLong long duration) { + this.startMonotonicTime = startMonotonicTime; + this.startTime = startTime; + this.duration = duration; + } + + void write(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_TIMEFRAME); + serializer.attributeLong(null, XML_ATTR_START_TIME, startTime); + serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime); + serializer.attributeLong(null, XML_ATTR_DURATION, duration); + serializer.endTag(null, XML_TAG_TIMEFRAME); + } + + static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException { + return new TimeFrame( + parser.getAttributeLong(null, XML_ATTR_MONOTONIC), + parser.getAttributeLong(null, XML_ATTR_START_TIME), + parser.getAttributeLong(null, XML_ATTR_DURATION)); + } + + /** + * Prints the contents of this TimeFrame. + */ + public void dump(IndentingPrintWriter pw) { + StringBuilder sb = new StringBuilder(); + sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime))) + .append(" (monotonic=").append(startMonotonicTime).append(") ") + .append(" duration="); + String durationString = TimeUtils.formatDuration(duration); + if (durationString.startsWith("+")) { + sb.append(durationString.substring(1)); + } else { + sb.append(durationString); + } + pw.print(sb); + } + } + + static class Metadata { + static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId); + + private final long mId; + private final List<TimeFrame> mTimeFrames = new ArrayList<>(); + private final List<String> mSections = new ArrayList<>(); + + Metadata(long id) { + mId = id; + } + + public long getId() { + return mId; + } + + public List<TimeFrame> getTimeFrames() { + return mTimeFrames; + } + + public List<String> getSections() { + return mSections; + } + + void addTimeFrame(TimeFrame timeFrame) { + mTimeFrames.add(timeFrame); + } + + void addSection(String sectionType) { + // The number of sections per span is small, so there is no need to use a Set + if (!mSections.contains(sectionType)) { + mSections.add(sectionType); + } + } + + void write(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_METADATA); + serializer.attributeLong(null, XML_ATTR_ID, mId); + serializer.attributeInt(null, XML_ATTR_VERSION, VERSION); + for (TimeFrame timeFrame : mTimeFrames) { + timeFrame.write(serializer); + } + for (String section : mSections) { + serializer.startTag(null, XML_TAG_SECTION); + serializer.attribute(null, XML_ATTR_SECTION_TYPE, section); + serializer.endTag(null, XML_TAG_SECTION); + } + serializer.endTag(null, XML_TAG_METADATA); + } + + /** + * Reads just the header of the XML file containing metadata. + * Returns null if the file does not contain a compatible <metadata> element. + */ + @Nullable + public static Metadata read(TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + Metadata metadata = null; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG + && parser.getName().equals(XML_TAG_METADATA))) { + if (eventType == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + if (tagName.equals(XML_TAG_METADATA)) { + int version = parser.getAttributeInt(null, XML_ATTR_VERSION); + if (!isCompatibleXmlFormat(version)) { + Slog.e(TAG, + "Incompatible version " + version + "; expected " + VERSION); + return null; + } + + long id = parser.getAttributeLong(null, XML_ATTR_ID); + metadata = new Metadata(id); + } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) { + metadata.addTimeFrame(TimeFrame.read(parser)); + } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) { + metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE)); + } + } + eventType = parser.next(); + } + return metadata; + } + + /** + * Prints the metadata. + */ + public void dump(IndentingPrintWriter pw) { + dump(pw, true); + } + + void dump(IndentingPrintWriter pw, boolean includeSections) { + pw.print("Span "); + if (mTimeFrames.size() > 0) { + mTimeFrames.get(0).dump(pw); + pw.println(); + } + + // Sometimes, when the wall clock is adjusted in the middle of a stats session, + // we will have more than one time frame. + for (int i = 1; i < mTimeFrames.size(); i++) { + TimeFrame timeFrame = mTimeFrames.get(i); + pw.print(" "); // Aligned below "Span " + timeFrame.dump(pw); + pw.println(); + } + + if (includeSections) { + pw.increaseIndent(); + for (String section : mSections) { + pw.print("section", section); + pw.println(); + } + pw.decreaseIndent(); + } + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + IndentingPrintWriter ipw = new IndentingPrintWriter(sw); + ipw.print("id", mId); + for (int i = 0; i < mTimeFrames.size(); i++) { + TimeFrame timeFrame = mTimeFrames.get(i); + ipw.print("timeframe=["); + timeFrame.dump(ipw); + ipw.print("] "); + } + for (String section : mSections) { + ipw.print("section", section); + } + ipw.flush(); + return sw.toString().trim(); + } + } + + /** + * Contains a specific type of aggregate power stats. The contents type is determined by + * the section type. + */ + public abstract static class Section { + private final String mType; + + Section(String type) { + mType = type; + } + + /** + * Returns the section type, which determines the type of data stored in the corresponding + * section of {@link PowerStatsSpan} + */ + public String getType() { + return mType; + } + + abstract void write(TypedXmlSerializer serializer) throws IOException; + + /** + * Prints the section type. + */ + public void dump(IndentingPrintWriter ipw) { + ipw.println(mType); + } + } + + /** + * A universal XML parser for {@link PowerStatsSpan.Section}'s. It is aware of all + * supported section types as well as their corresponding XML formats. + */ + public interface SectionReader { + /** + * Reads the contents of the section using the parser. The type of the object + * read and the corresponding XML format are determined by the section type. + */ + Section read(String sectionType, TypedXmlPullParser parser) + throws IOException, XmlPullParserException; + } + + private final Metadata mMetadata; + private final List<Section> mSections = new ArrayList<>(); + + public PowerStatsSpan(long id) { + this(new Metadata(id)); + } + + private PowerStatsSpan(Metadata metadata) { + mMetadata = metadata; + } + + public Metadata getMetadata() { + return mMetadata; + } + + public long getId() { + return mMetadata.mId; + } + + void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime, + @DurationMillisLong long duration) { + mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration)); + } + + void addSection(Section section) { + mMetadata.addSection(section.getType()); + mSections.add(section); + } + + @NonNull + public List<Section> getSections() { + return mSections; + } + + private static boolean isCompatibleXmlFormat(int version) { + return version == VERSION; + } + + /** + * Creates an XML file containing the persistent state of the power stats span. + */ + @VisibleForTesting + public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + mMetadata.write(serializer); + for (Section section : mSections) { + serializer.startTag(null, XML_TAG_SECTION); + serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType); + section.write(serializer); + serializer.endTag(null, XML_TAG_SECTION); + } + serializer.endDocument(); + } + + @Nullable + static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser, + SectionReader sectionReader, String... sectionTypes) + throws IOException, XmlPullParserException { + Set<String> neededSections = Sets.newArraySet(sectionTypes); + boolean selectSections = !neededSections.isEmpty(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + Metadata metadata = Metadata.read(parser); + if (metadata == null) { + return null; + } + + PowerStatsSpan span = new PowerStatsSpan(metadata); + boolean skipSection = false; + int nestingLevel = 0; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (skipSection) { + if (eventType == XmlPullParser.END_TAG + && parser.getName().equals(XML_TAG_SECTION)) { + nestingLevel--; + if (nestingLevel == 0) { + skipSection = false; + } + } else if (eventType == XmlPullParser.START_TAG + && parser.getName().equals(XML_TAG_SECTION)) { + nestingLevel++; + } + } else if (eventType == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if (tag.equals(XML_TAG_SECTION)) { + String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE); + if (!selectSections || neededSections.contains(sectionType)) { + Section section = sectionReader.read(sectionType, parser); + if (section == null) { + if (selectSections) { + throw new XmlPullParserException( + "Unsupported PowerStatsStore section type: " + sectionType); + } else { + section = new Section(sectionType) { + @Override + public void dump(IndentingPrintWriter ipw) { + ipw.println("Unsupported PowerStatsStore section type: " + + sectionType); + } + + @Override + void write(TypedXmlSerializer serializer) { + } + }; + } + } + span.addSection(section); + } else { + skipSection = true; + } + } else if (tag.equals(XML_TAG_METADATA)) { + Metadata.read(parser); + } + } + eventType = parser.next(); + } + return span; + } + + /** + * Prints the contents of this power stats span. + */ + public void dump(IndentingPrintWriter ipw) { + mMetadata.dump(ipw, /* includeSections */ false); + for (Section section : mSections) { + ipw.increaseIndent(); + ipw.println(section.mType); + section.dump(ipw); + ipw.decreaseIndent(); + } + } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java new file mode 100644 index 000000000000..7123bcb2d095 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java @@ -0,0 +1,371 @@ +/* + * 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.power.stats; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.BatteryUsageStats; +import android.os.FileUtils; +import android.os.Handler; +import android.util.AtomicFile; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * A storage mechanism for aggregated power/battery stats. + */ +public class PowerStatsStore { + private static final String TAG = "PowerStatsStore"; + + private static final String POWER_STATS_DIR = "power-stats"; + private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss"; + private static final String DIR_LOCK_FILENAME = ".lock"; + private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024; + + private final File mSystemDir; + private final File mStoreDir; + private final File mLockFile; + private final ReentrantLock mFileLock = new ReentrantLock(); + private FileLock mJvmLock; + private final long mMaxStorageBytes; + private final Handler mHandler; + private final PowerStatsSpan.SectionReader mSectionReader; + private volatile List<PowerStatsSpan.Metadata> mTableOfContents; + + public PowerStatsStore(@NonNull File systemDir, Handler handler, + AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { + this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler, + new DefaultSectionReader(aggregatedPowerStatsConfig)); + } + + @VisibleForTesting + public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler, + @NonNull PowerStatsSpan.SectionReader sectionReader) { + mSystemDir = systemDir; + mStoreDir = new File(systemDir, POWER_STATS_DIR); + mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); + mHandler = handler; + mMaxStorageBytes = maxStorageBytes; + mSectionReader = sectionReader; + mHandler.post(this::maybeClearLegacyStore); + } + + /** + * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store. + */ + @NonNull + public List<PowerStatsSpan.Metadata> getTableOfContents() { + List<PowerStatsSpan.Metadata> toc = mTableOfContents; + if (toc != null) { + return toc; + } + + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + lockStoreDirectory(); + try { + toc = new ArrayList<>(); + for (File file : mStoreDir.listFiles()) { + String fileName = file.getName(); + if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { + continue; + } + try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + parser.setInput(inputStream, StandardCharsets.UTF_8.name()); + PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser); + if (metadata != null) { + toc.add(metadata); + } else { + Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName); + file.delete(); + } + } catch (IOException | XmlPullParserException e) { + Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName); + } + } + toc.sort(PowerStatsSpan.Metadata.COMPARATOR); + mTableOfContents = Collections.unmodifiableList(toc); + } finally { + unlockStoreDirectory(); + } + + return toc; + } + + /** + * Saves the specified span in the store. + */ + public void storePowerStatsSpan(PowerStatsSpan span) { + maybeClearLegacyStore(); + lockStoreDirectory(); + try { + if (!mStoreDir.exists()) { + if (!mStoreDir.mkdirs()) { + Slog.e(TAG, "Could not create a directory for power stats store"); + return; + } + } + + AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId())); + file.write(out-> { + try { + span.writeXml(out, Xml.newBinarySerializer()); + } catch (Exception e) { + // AtomicFile will log the exception and delete the file. + throw new RuntimeException(e); + } + }); + mTableOfContents = null; + removeOldSpansLocked(); + } finally { + unlockStoreDirectory(); + } + } + + /** + * Loads the PowerStatsSpan identified by its ID. Only loads the sections with + * the specified types. Loads all sections if no sectionTypes is empty. + */ + @Nullable + public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) { + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + lockStoreDirectory(); + try { + File file = makePowerStatsSpanFilename(id); + try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes); + } catch (IOException | XmlPullParserException e) { + Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file); + } + } finally { + unlockStoreDirectory(); + } + return null; + } + + void storeAggregatedPowerStats(AggregatedPowerStats stats) { + PowerStatsSpan span = createPowerStatsSpan(stats); + if (span == null) { + return; + } + storePowerStatsSpan(span); + } + + static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) { + List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates(); + if (clockUpdates.isEmpty()) { + Slog.w(TAG, "No clock updates in aggregated power stats " + stats); + return null; + } + + long monotonicTime = clockUpdates.get(0).monotonicTime; + long durationSum = 0; + PowerStatsSpan span = new PowerStatsSpan(monotonicTime); + for (int i = 0; i < clockUpdates.size(); i++) { + AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i); + long duration; + if (i == clockUpdates.size() - 1) { + duration = stats.getDuration() - durationSum; + } else { + duration = clockUpdate.monotonicTime - monotonicTime; + } + span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration); + monotonicTime = clockUpdate.monotonicTime; + durationSum += duration; + } + + span.addSection(new AggregatedPowerStatsSection(stats)); + return span; + } + + /** + * Stores a {@link PowerStatsSpan} containing a single section for the supplied + * battery usage stats. + */ + public void storeBatteryUsageStats(long monotonicStartTime, + BatteryUsageStats batteryUsageStats) { + PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime); + span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(), + batteryUsageStats.getStatsDuration()); + span.addSection(new BatteryUsageStatsSection(batteryUsageStats)); + storePowerStatsSpan(span); + } + + /** + * Creates a file name by formatting the span ID as a 19-digit zero-padded number. + * This ensures that the lexicographically sorted directory follows the chronological order. + */ + private File makePowerStatsSpanFilename(long id) { + return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id) + + POWER_STATS_SPAN_FILE_EXTENSION); + } + + private void maybeClearLegacyStore() { + File legacyStoreDir = new File(mSystemDir, "battery-usage-stats"); + if (legacyStoreDir.exists()) { + FileUtils.deleteContentsAndDir(legacyStoreDir); + } + } + + private void lockStoreDirectory() { + mFileLock.lock(); + + // Lock the directory from access by other JVMs + try { + mLockFile.getParentFile().mkdirs(); + mLockFile.createNewFile(); + mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); + } catch (IOException e) { + Slog.e(TAG, "Cannot lock snapshot directory", e); + } + } + + private void unlockStoreDirectory() { + try { + mJvmLock.close(); + } catch (IOException e) { + Slog.e(TAG, "Cannot unlock snapshot directory", e); + } finally { + mFileLock.unlock(); + } + } + + private void removeOldSpansLocked() { + // Read the directory list into a _sorted_ map. The alphanumeric ordering + // corresponds to the historical order of snapshots because the file names + // are timestamps zero-padded to the same length. + long totalSize = 0; + TreeMap<File, Long> mFileSizes = new TreeMap<>(); + for (File file : mStoreDir.listFiles()) { + final long fileSize = file.length(); + totalSize += fileSize; + if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { + mFileSizes.put(file, fileSize); + } + } + + while (totalSize > mMaxStorageBytes) { + final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); + if (entry == null) { + break; + } + + File file = entry.getKey(); + if (!file.delete()) { + Slog.e(TAG, "Cannot delete power stats span " + file); + } + totalSize -= entry.getValue(); + mFileSizes.remove(file); + mTableOfContents = null; + } + } + + /** + * Deletes all contents from the store. + */ + public void reset() { + lockStoreDirectory(); + try { + for (File file : mStoreDir.listFiles()) { + if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { + if (!file.delete()) { + Slog.e(TAG, "Cannot delete power stats span " + file); + } + } + } + mTableOfContents = List.of(); + } finally { + unlockStoreDirectory(); + } + } + + /** + * Prints the summary of contents of the store: only metadata, but not the actual stored + * objects. + */ + public void dumpTableOfContents(IndentingPrintWriter ipw) { + ipw.println("Power stats store TOC"); + ipw.increaseIndent(); + List<PowerStatsSpan.Metadata> contents = getTableOfContents(); + for (PowerStatsSpan.Metadata metadata : contents) { + metadata.dump(ipw); + } + ipw.decreaseIndent(); + } + + /** + * Prints the contents of the store. + */ + public void dump(IndentingPrintWriter ipw) { + ipw.println("Power stats store"); + ipw.increaseIndent(); + List<PowerStatsSpan.Metadata> contents = getTableOfContents(); + for (PowerStatsSpan.Metadata metadata : contents) { + PowerStatsSpan span = loadPowerStatsSpan(metadata.getId()); + if (span != null) { + span.dump(ipw); + } + } + ipw.decreaseIndent(); + } + + private static class DefaultSectionReader implements PowerStatsSpan.SectionReader { + private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + + DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { + mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; + } + + @Override + public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + switch (sectionType) { + case AggregatedPowerStatsSection.TYPE: + return new AggregatedPowerStatsSection( + AggregatedPowerStats.createFromXml(parser, + mAggregatedPowerStatsConfig)); + case BatteryUsageStatsSection.TYPE: + return new BatteryUsageStatsSection( + BatteryUsageStats.createFromXml(parser)); + default: + return null; + } + } + } +} 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 d61bebc0c82c..0f135715ebc3 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -1,8 +1,15 @@ package: "com.android.server.power.optimization" flag { + name: "power_monitor_api" + namespace: "backstage_power" + description: "Feature flag for ODPM API" + bug: "295027807" +} + +flag { name: "streamlined_battery_stats" - namespace: "power_optimization" + namespace: "backstage_power" description: "Feature flag for streamlined battery stats" bug: "285646152" } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 5609f6975bc4..77290fd944eb 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 c9db343697df..0656a6a7d3e9 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/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bff6d502d566..6d580e97d578 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -39,8 +39,10 @@ import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.modules.expresslog.Counter; import com.android.server.infra.AbstractPerUserSystemService; import java.util.HashMap; @@ -64,6 +66,9 @@ final class SpeechRecognitionManagerServiceImpl extends private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid = new HashMap<>(); + @GuardedBy("mLock") + private final SparseIntArray mSessionCountByUid = new SparseIntArray(); + SpeechRecognitionManagerServiceImpl( @NonNull SpeechRecognitionManagerService master, @NonNull Object lock, @UserIdInt int userId) { @@ -216,6 +221,7 @@ final class SpeechRecognitionManagerServiceImpl extends service.shutdown(clientToken); } synchronized (mLock) { + decrementSessionCountForUidLocked(callingUid); if (!service.hasActiveSessions()) { removeService(callingUid, service); } @@ -239,6 +245,26 @@ final class SpeechRecognitionManagerServiceImpl extends return ComponentName.unflattenFromString(serviceName); } + @GuardedBy("mLock") + private int getSessionCountByUidLocked(int uid) { + return mSessionCountByUid.get(uid, 0); + } + + @GuardedBy("mLock") + private void incrementSessionCountForUidLocked(int uid) { + mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1); + } + + @GuardedBy("mLock") + private void decrementSessionCountForUidLocked(int uid) { + int newCount = mSessionCountByUid.get(uid, 1) - 1; + if (newCount > 0) { + mSessionCountByUid.put(uid, newCount); + } else { + mSessionCountByUid.delete(uid); + } + } + private RemoteSpeechRecognitionService createService( int callingUid, ComponentName serviceComponent) { synchronized (mLock) { @@ -247,6 +273,18 @@ final class SpeechRecognitionManagerServiceImpl extends if (servicesForClient != null && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_service_connections_count", + callingUid); + return null; + } + + if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_session_count", + callingUid); return null; } @@ -262,6 +300,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Reused existing connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return existingService.get(); } } @@ -282,6 +321,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Creating a new connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return service; } } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 174ad3ad2e25..c33f3d9fa264 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,11 +1,10 @@ jeffreyhuang@google.com joeo@google.com -jtnguyen@google.com +monicamwang@google.com muhammadq@google.com +rayhdez@google.com rslawik@google.com -ruchirr@google.com sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com -yro@google.com diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index bfe34049e1e5..9a9b83602832 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 a5c0fb3c46af..cddc79db6106 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 e4f960763d54..a34621642bcd 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/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index becbbf2ea76a..519acec2f7b4 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -22,11 +22,10 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; +import android.os.vibrator.Flags; import android.util.Slog; import android.util.SparseArray; import android.view.HapticFeedbackConstants; -import android.view.flags.FeatureFlags; -import android.view.flags.FeatureFlagsImpl; import com.android.internal.annotations.VisibleForTesting; @@ -56,7 +55,8 @@ public final class HapticFeedbackVibrationProvider { // If present and valid, a vibration here will be used for an effect. // Otherwise, the system's default vibration will be used. @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations; - private final FeatureFlags mViewFeatureFlags; + + private float mKeyboardVibrationFixedAmplitude; /** @hide */ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { @@ -65,16 +65,14 @@ public final class HapticFeedbackVibrationProvider { /** @hide */ public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) { - this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo), - new FeatureFlagsImpl()); + this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo)); } /** @hide */ @VisibleForTesting HapticFeedbackVibrationProvider( Resources res, VibratorInfo vibratorInfo, - @Nullable SparseArray<VibrationEffect> hapticCustomizations, - FeatureFlags viewFeatureFlags) { + @Nullable SparseArray<VibrationEffect> hapticCustomizations) { mVibratorInfo = vibratorInfo; mHapticTextHandleEnabled = res.getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); @@ -83,14 +81,17 @@ public final class HapticFeedbackVibrationProvider { hapticCustomizations = null; } mHapticCustomizations = hapticCustomizations; - mViewFeatureFlags = viewFeatureFlags; - mSafeModeEnabledVibrationEffect = effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED) ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED) : VibrationSettings.createEffectFromResource( res, com.android.internal.R.array.config_safeModeEnabledVibePattern); + mKeyboardVibrationFixedAmplitude = res.getFloat( + com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude); + if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) { + mKeyboardVibrationFixedAmplitude = -1; + } } /** @@ -120,6 +121,9 @@ public final class HapticFeedbackVibrationProvider { return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK); case HapticFeedbackConstants.KEYBOARD_RELEASE: + case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS + return getKeyboardVibration(effectId); + case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: case HapticFeedbackConstants.ENTRY_BUMP: case HapticFeedbackConstants.DRAG_CROSSING: @@ -128,7 +132,6 @@ public final class HapticFeedbackVibrationProvider { VibrationEffect.EFFECT_TICK, /* fallbackForPredefinedEffect= */ false); - case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS case HapticFeedbackConstants.VIRTUAL_KEY: case HapticFeedbackConstants.EDGE_RELEASE: case HapticFeedbackConstants.CALENDAR_DATE: @@ -204,6 +207,10 @@ public final class HapticFeedbackVibrationProvider { case HapticFeedbackConstants.SCROLL_LIMIT: attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; break; + case HapticFeedbackConstants.KEYBOARD_TAP: + case HapticFeedbackConstants.KEYBOARD_RELEASE: + attrs = createKeyboardVibrationAttributes(); + break; default: attrs = TOUCH_VIBRATION_ATTRIBUTES; } @@ -212,9 +219,12 @@ public final class HapticFeedbackVibrationProvider { if (bypassVibrationIntensitySetting) { flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; } - if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) { + if (shouldBypassInterruptionPolicy(effectId)) { flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; } + if (shouldBypassIntensityScale(effectId)) { + flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; + } return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build(); } @@ -295,6 +305,64 @@ public final class HapticFeedbackVibrationProvider { return mHapticCustomizations != null && mHapticCustomizations.contains(effectId); } + private VibrationEffect getKeyboardVibration(int effectId) { + if (effectHasCustomization(effectId)) { + return mHapticCustomizations.get(effectId); + } + + int primitiveId; + int predefinedEffectId; + boolean predefinedEffectFallback; + + switch (effectId) { + case HapticFeedbackConstants.KEYBOARD_RELEASE: + primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK; + predefinedEffectId = VibrationEffect.EFFECT_TICK; + predefinedEffectFallback = false; + break; + case HapticFeedbackConstants.KEYBOARD_TAP: + default: + primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK; + predefinedEffectId = VibrationEffect.EFFECT_CLICK; + predefinedEffectFallback = true; + } + if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) { + if (mVibratorInfo.isPrimitiveSupported(primitiveId)) { + return VibrationEffect.startComposition() + .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude) + .compose(); + } + } + return getVibration(effectId, predefinedEffectId, + /* fallbackForPredefinedEffect= */ predefinedEffectFallback); + } + + private boolean shouldBypassIntensityScale(int effectId) { + if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) { + // shouldn't bypass if not support keyboard category or no fixed amplitude + return false; + } + switch (effectId) { + case HapticFeedbackConstants.KEYBOARD_TAP: + return mVibratorInfo.isPrimitiveSupported( + VibrationEffect.Composition.PRIMITIVE_CLICK); + case HapticFeedbackConstants.KEYBOARD_RELEASE: + return mVibratorInfo.isPrimitiveSupported( + VibrationEffect.Composition.PRIMITIVE_TICK); + } + return false; + } + + private static VibrationAttributes createKeyboardVibrationAttributes() { + if (!Flags.keyboardCategoryEnabled()) { + return TOUCH_VIBRATION_ATTRIBUTES; + } + + return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build(); + } + @Nullable private static SparseArray<VibrationEffect> loadHapticCustomizations( Resources res, VibratorInfo vibratorInfo) { @@ -306,8 +374,7 @@ public final class HapticFeedbackVibrationProvider { } } - private static boolean shouldBypassInterruptionPolicy( - int effectId, FeatureFlags viewFeatureFlags) { + private static boolean shouldBypassInterruptionPolicy(int effectId) { switch (effectId) { case HapticFeedbackConstants.SCROLL_TICK: case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: @@ -315,7 +382,7 @@ public final class HapticFeedbackVibrationProvider { // The SCROLL_* constants should bypass interruption filter, so that scroll haptics // can play regardless of focus modes like DND. Guard this behavior by the feature // flag controlling the general scroll feedback APIs. - return viewFeatureFlags.scrollFeedbackApi(); + return android.view.flags.Flags.scrollFeedbackApi(); default: return false; } diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS index 9afa68210947..da5a476e9eb8 100644 --- a/services/core/java/com/android/server/vibrator/OWNERS +++ b/services/core/java/com/android/server/vibrator/OWNERS @@ -1,6 +1,5 @@ # Bug component: 345036 - +khalilahmad@google.com lsandrade@google.com michaelwr@google.com -sbowden@google.com -khalilahmad@google.com
\ No newline at end of file +roosa@google.com diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index db8a9ae553cd..1d5cac54d12b 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY; import static android.os.VibrationAttributes.USAGE_ALARM; import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; @@ -52,6 +53,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.Vibrator.VibrationIntensity; +import android.os.vibrator.Flags; import android.os.vibrator.VibrationConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; @@ -188,6 +190,8 @@ final class VibrationSettings { @GuardedBy("mLock") private boolean mVibrateOn; @GuardedBy("mLock") + private boolean mKeyboardVibrationOn; + @GuardedBy("mLock") private int mRingerMode; @GuardedBy("mLock") private boolean mOnWirelessCharger; @@ -295,6 +299,8 @@ final class VibrationSettings { Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY)); registerSettingsObserver( Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.KEYBOARD_VIBRATION_ENABLED)); if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) { Intent batteryStatus = mContext.registerReceiver( @@ -418,14 +424,9 @@ final class VibrationSettings { } if (!callerInfo.attrs.isFlagSet( - VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { - if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { - return Vibration.Status.IGNORED_FOR_SETTINGS; - } - - if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) { - return Vibration.Status.IGNORED_FOR_SETTINGS; - } + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + && !shouldVibrateForUserSetting(callerInfo)) { + return Vibration.Status.IGNORED_FOR_SETTINGS; } if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { @@ -497,6 +498,30 @@ final class VibrationSettings { return mRingerMode != AudioManager.RINGER_MODE_SILENT; } + /** + * Return {@code true} if the device should vibrate for user setting, and + * {@code false} to ignore the vibration. + */ + @GuardedBy("mLock") + private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) { + final int usage = callerInfo.attrs.getUsage(); + if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { + // Main setting disabled. + return false; + } + + if (Flags.keyboardCategoryEnabled()) { + int category = callerInfo.attrs.getCategory(); + if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) { + // Keyboard touch has a different user setting. + return mKeyboardVibrationOn; + } + } + + // Apply individual user setting based on usage. + return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF; + } + /** Update all cached settings and triggers registered listeners. */ void update() { updateSettings(); @@ -508,6 +533,8 @@ final class VibrationSettings { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; + mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, + mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0; int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1), diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 45bd1521bc35..ace7777c9b58 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -97,7 +97,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new VibrationAttributes.Builder().build(); private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY - | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; @@ -771,8 +772,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Vibration.EndInfo startVibrationLocked(HalVibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - // Scale effect before dispatching it to the input devices or the vibration thread. - vib.scaleEffects(mVibrationScaler::scale); + if (!vib.callerInfo.attrs.isFlagSet( + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) { + // Scale effect before dispatching it to the input devices or the vibration thread. + vib.scaleEffects(mVibrationScaler::scale); + } boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.callerInfo, vib.getEffectToPlay()); if (inputDevicesAvailable) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 01ea33f1aecd..4200fbf39ade 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} @@ -3982,7 +3982,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaper == null) { // common case, this is the first lookup post-boot of the system or // unified lock, so we bring up the saved state lazily now and recheck. - int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM; + // if we're loading the system wallpaper for the first time, also load the lock + // wallpaper to determine if the system wallpaper is system+lock or system only. + int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK; loadSettingsLocked(userId, false, whichLoad); wallpaper = whichSet.get(userId); if (wallpaper == null) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c866dd013af0..a01113b26a1e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1316,6 +1316,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mLaunchIntoPipHostActivity != null) { pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity); } + if (mWaitForEnteringPinnedMode) { + pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true"); + } mLetterboxUiController.dump(pw, prefix); @@ -3132,9 +3135,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } boolean canReceiveKeys() { - // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent - return getWindowConfiguration().canReceiveKeys() - && (task == null || task.getWindowConfiguration().canReceiveKeys()); + return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode; } boolean isResizeable() { @@ -8264,7 +8265,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 +8277,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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b673211a1c9..de335d3d013e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getTransitionController(), mWindowManager.mSyncEngine) : null; + if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride() + && transition != null) { + transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + } + final Runnable enterPipRunnable = () -> { synchronized (mGlobalLock) { if (r.getParent() == null) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 12e1e2cd1e41..777b5cd4337b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2832,17 +2832,22 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { static class OpaqueActivityHelper implements Predicate<ActivityRecord> { private ActivityRecord mStarting; private boolean mIncludeInvisibleAndFinishing; + private boolean mIgnoringKeyguard; - ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { + ActivityRecord getOpaqueActivity( + @NonNull WindowContainer<?> container, boolean ignoringKeyguard) { mIncludeInvisibleAndFinishing = true; + mIgnoringKeyguard = ignoringKeyguard; return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); } - ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container, - @Nullable ActivityRecord starting) { + ActivityRecord getVisibleOpaqueActivity( + @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting, + boolean ignoringKeyguard) { mStarting = starting; mIncludeInvisibleAndFinishing = false; + mIgnoringKeyguard = ignoringKeyguard; final ActivityRecord opaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); mStarting = null; @@ -2851,7 +2856,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { @Override public boolean test(ActivityRecord r) { - if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) { + if (!mIncludeInvisibleAndFinishing && r != mStarting + && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard) + || (!mIgnoringKeyguard && !r.isVisible()))) { // Ignore invisible activities that are not the currently starting activity // (about to be visible). return false; diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index b039646c1697..3dc377dbc14c 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -22,6 +22,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -31,7 +32,8 @@ import java.io.PrintWriter; * Interface that describes an animation and bridges the animation start to the component * responsible for running the animation. */ -interface AnimationAdapter { +@VisibleForTesting +public interface AnimationAdapter { long STATUS_BAR_TRANSITION_DURATION = 120L; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 42376685498a..6d59b297de48 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -276,6 +276,10 @@ class BackNavigationController { // activity, we won't close the activity. backType = BackNavigationInfo.TYPE_DIALOG_CLOSE; removedWindowContainer = window; + } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) { + // skip if current activity is translucent + backType = BackNavigationInfo.TYPE_CALLBACK; + removedWindowContainer = window; } else if (prevActivity != null) { if (!isOccluded || prevActivity.canShowWhenLocked()) { // We have another Activity in the same currentTask to go to diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index ae29afa9fc49..823fbc9b0f3e 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -11,172 +11,39 @@ * 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 + * limitations under the License. */ package com.android.server.wm; -import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.AlphaAnimationSpecProto.FROM; -import static com.android.server.wm.AlphaAnimationSpecProto.TO; -import static com.android.server.wm.AnimationSpecProto.ALPHA; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; - import android.annotation.NonNull; import android.graphics.Rect; -import android.util.Log; -import android.util.proto.ProtoOutputStream; -import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.SurfaceAnimator.AnimationType; - -import java.io.PrintWriter; +import com.android.window.flags.Flags; /** * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is * black layers of varying opacity at various Z-levels which create the effect of a Dim. */ -class Dimmer { - private static final String TAG = "WindowManager"; - // This is in milliseconds. - private static final int DEFAULT_DIM_ANIM_DURATION = 200; - - private class DimAnimatable implements SurfaceAnimator.Animatable { - private SurfaceControl mDimLayer; - - private DimAnimatable(SurfaceControl dimLayer) { - mDimLayer = dimLayer; - } - - @Override - public SurfaceControl.Transaction getSyncTransaction() { - return mHost.getSyncTransaction(); - } - - @Override - public SurfaceControl.Transaction getPendingTransaction() { - return mHost.getPendingTransaction(); - } - - @Override - public void commitPendingTransaction() { - mHost.commitPendingTransaction(); - } - - @Override - public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { - } - - @Override - public void onAnimationLeashLost(SurfaceControl.Transaction t) { - } +public abstract class Dimmer { - @Override - public SurfaceControl.Builder makeAnimationLeash() { - return mHost.makeAnimationLeash(); - } - - @Override - public SurfaceControl getAnimationLeashParent() { - return mHost.getSurfaceControl(); - } - - @Override - public SurfaceControl getSurfaceControl() { - return mDimLayer; - } - - @Override - public SurfaceControl getParentSurfaceControl() { - return mHost.getSurfaceControl(); - } - - @Override - public int getSurfaceWidth() { - // This will determine the size of the leash created. This should be the size of the - // host and not the dim layer since the dim layer may get bigger during animation. If - // that occurs, the leash size cannot change so we need to ensure the leash is big - // enough that the dim layer can grow. - // This works because the mHost will be a Task which has the display bounds. - return mHost.getSurfaceWidth(); - } - - @Override - public int getSurfaceHeight() { - // See getSurfaceWidth() above for explanation. - return mHost.getSurfaceHeight(); - } - - void removeSurface() { - if (mDimLayer != null && mDimLayer.isValid()) { - getSyncTransaction().remove(mDimLayer); - } - mDimLayer = null; - } - } - - @VisibleForTesting - class DimState { - /** - * The layer where property changes should be invoked on. - */ - SurfaceControl mDimLayer; - boolean mDimming; - boolean isVisible; - SurfaceAnimator mSurfaceAnimator; - - // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. - final Rect mDimBounds = new Rect(); - - /** - * Determines whether the dim layer should animate before destroying. - */ - boolean mAnimateExit = true; - - /** - * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for - * details on Dim lifecycle. - */ - boolean mDontReset; - - DimState(SurfaceControl dimLayer) { - mDimLayer = dimLayer; - mDimming = true; - final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); - mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { - if (!mDimming) { - dimAnimatable.removeSurface(); - } - }, mHost.mWmService); - } - } + static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor(); /** - * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the + * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the * host, some controller of it, or one of the hosts children. */ - private WindowContainer mHost; - private WindowContainer mLastRequestedDimContainer; - @VisibleForTesting - DimState mDimState; - - private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; + protected final WindowContainer mHost; - @VisibleForTesting - interface SurfaceAnimatorStarter { - void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type); - } - - Dimmer(WindowContainer host) { - this(host, SurfaceAnimator::startAnimation); + protected Dimmer(WindowContainer host) { + mHost = host; } - Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { - mHost = host; - mSurfaceAnimatorStarter = surfaceAnimatorStarter; + // Constructs the correct type of dimmer + static Dimmer create(WindowContainer host) { + return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host); } @NonNull @@ -184,73 +51,34 @@ class Dimmer { return mHost; } - private SurfaceControl makeDimLayer() { - return mHost.makeChildSurface(null) - .setParent(mHost.getSurfaceControl()) - .setColorLayer() - .setName("Dim Layer for - " + mHost.getName()) - .setCallsite("Dimmer.makeDimLayer") - .build(); - } - /** - * Retrieve the DimState, creating one if it doesn't exist. - */ - private DimState getDimState(WindowContainer container) { - if (mDimState == null) { - try { - final SurfaceControl ctl = makeDimLayer(); - mDimState = new DimState(ctl); - } catch (Surface.OutOfResourcesException e) { - Log.w(TAG, "OutOfResourcesException creating dim surface"); - } - } - - mLastRequestedDimContainer = container; - return mDimState; - } - - private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { - final DimState d = getDimState(container); - - if (d == null) { - return; - } - - // The dim method is called from WindowState.prepareSurfaces(), which is always called - // in the correct Z from lowest Z to highest. This ensures that the dim layer is always - // relative to the highest Z layer with a dim. - SurfaceControl.Transaction t = mHost.getPendingTransaction(); - t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); - t.setAlpha(d.mDimLayer, alpha); - t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); - - d.mDimming = true; - } - - /** - * Place a dim above the given container, which should be a child of the host container. - * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset - * and the child should call dimAbove again to request the Dim to continue. + * Position the dim relatively to the dimming container. + * Normally called together with #setAppearance, it can be called alone to keep the dim parented + * to a visible container until the next dimming container is ready. + * If multiple containers call this method, only the changes relative to the topmost will be + * applied. * - * @param container The container which to dim above. Should be a child of our host. - * @param alpha The alpha at which to Dim. + * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and + * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to + * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState + * without also adjusting the appearance. + * @param container The container which to dim above. Should be a child of the host. + * @param relativeLayer The position of the dim wrt the container */ - void dimAbove(@NonNull WindowContainer container, float alpha) { - dim(container, 1, alpha, 0); - } + protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer); /** - * Like {@link #dimAbove} but places the dim below the given container. - * - * @param container The container which to dim below. Should be a child of our host. - * @param alpha The alpha at which to Dim. - * @param blurRadius The amount of blur added to the Dim. + * Set the aspect of the dim layer, and request to keep dimming. + * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the + * child should call setAppearance again to request the Dim to continue. + * If multiple containers call this method, only the changes relative to the topmost will be + * applied. + * @param container Container requesting the dim + * @param alpha Dim amount + * @param blurRadius Blur amount */ - - void dimBelow(@NonNull WindowContainer container, float alpha, int blurRadius) { - dim(container, -1, alpha, blurRadius); - } + protected abstract void adjustAppearance( + WindowContainer container, float alpha, int blurRadius); /** * Mark all dims as pending completion on the next call to {@link #updateDims} @@ -260,25 +88,15 @@ class Dimmer { * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them * a chance to request dims to continue. */ - void resetDimStates() { - if (mDimState == null) { - return; - } - if (!mDimState.mDontReset) { - mDimState.mDimming = false; - } - } + abstract void resetDimStates(); /** Returns non-null bounds if the dimmer is showing. */ - Rect getDimBounds() { - return mDimState != null ? mDimState.mDimBounds : null; - } + abstract Rect getDimBounds(); - void dontAnimateExit() { - if (mDimState != null) { - mDimState.mAnimateExit = false; - } - } + abstract void dontAnimateExit(); + + @VisibleForTesting + abstract SurfaceControl getDimLayer(); /** * Call after invoking {@link WindowContainer#prepareSurfaces} on children as @@ -288,109 +106,5 @@ class Dimmer { * @param t A transaction in which to update the dims. * @return true if any Dims were updated. */ - boolean updateDims(SurfaceControl.Transaction t) { - if (mDimState == null) { - return false; - } - - if (!mDimState.mDimming) { - if (!mDimState.mAnimateExit) { - if (mDimState.mDimLayer.isValid()) { - t.remove(mDimState.mDimLayer); - } - } else { - startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); - } - mDimState = null; - return false; - } else { - final Rect bounds = mDimState.mDimBounds; - // TODO: Once we use geometry from hierarchy this falls away. - t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); - t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); - if (!mDimState.isVisible) { - mDimState.isVisible = true; - t.show(mDimState.mDimLayer); - // Skip enter animation while starting window is on top of its activity - final WindowState ws = mLastRequestedDimContainer.asWindowState(); - if (ws == null || ws.mActivityRecord == null - || ws.mActivityRecord.mStartingData == null) { - startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); - } - } - return true; - } - } - - private void startDimEnter(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t) { - startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); - } - - private void startDimExit(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t) { - startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); - } - - private void startAnim(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t, float startAlpha, float endAlpha) { - mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( - new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), - mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, - ANIMATION_TYPE_DIMMER); - } - - private long getDimDuration(WindowContainer container) { - // If there's no container, then there isn't an animation occurring while dimming. Set the - // duration to 0 so it immediately dims to the set alpha. - if (container == null) { - return 0; - } - - // Otherwise use the same duration as the animation on the WindowContainer - AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); - final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); - return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) - : animationAdapter.getDurationHint(); - } - - private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { - private final long mDuration; - private final float mFromAlpha; - private final float mToAlpha; - - AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { - mFromAlpha = fromAlpha; - mToAlpha = toAlpha; - mDuration = duration; - } - - @Override - public long getDuration() { - return mDuration; - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { - final float fraction = getFraction(currentPlayTime); - final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; - t.setAlpha(sc, alpha); - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); - pw.print(" to="); pw.print(mToAlpha); - pw.print(" duration="); pw.println(mDuration); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(ALPHA); - proto.write(FROM, mFromAlpha); - proto.write(TO, mToAlpha); - proto.write(DURATION_MS, mDuration); - proto.end(token); - } - } + abstract boolean updateDims(SurfaceControl.Transaction t); } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index df26b101a657..f51bf7fbf27a 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -54,7 +54,6 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; - /** * Container for grouping WindowContainer below DisplayContent. * @@ -786,7 +785,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { * DisplayArea that can be dimmed. */ static class Dimmable extends DisplayArea<DisplayArea> { - private final Dimmer mDimmer = new Dimmer(this); + private final Dimmer mDimmer = Dimmer.create(this); Dimmable(WindowManagerService wms, Type type, String name, int featureId) { super(wms, type, name, featureId); diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index c21930dab5eb..1fa7d2a2aa13 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -51,7 +51,8 @@ class InputConsumerImpl implements IBinder.DeathRecipient { private final Rect mOldWindowCrop = new Rect(); InputConsumerImpl(WindowManagerService service, IBinder token, String name, - InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) { + InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId, + SurfaceControl.Transaction t) { mService = service; mToken = token; mName = name; @@ -82,6 +83,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient { .setName("Input Consumer " + name) .setCallsite("InputConsumerImpl") .build(); + mWindowHandle.setTrustedOverlay(t, mInputSurface, true); } void linkToDeathRecipient() { @@ -129,14 +131,12 @@ class InputConsumerImpl implements IBinder.DeathRecipient { void show(SurfaceControl.Transaction t, WindowContainer w) { t.show(mInputSurface); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1); } void show(SurfaceControl.Transaction t, int layer) { t.show(mInputSurface); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setLayer(mInputSurface, layer); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index af307ec3c2a9..5c0bc28779a8 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -224,7 +224,7 @@ final class InputMonitor { } final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name, - inputChannel, clientPid, clientUser, mDisplayId); + inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction); switch (name) { case INPUT_CONSUMER_WALLPAPER: consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER; @@ -675,11 +675,6 @@ final class InputMonitor { w.getKeyInterceptionInfo()); if (w.mWinAnimator.hasSurface()) { - // Update trusted overlay changes here because they are tied to input info. Input - // changes can be updated even if surfaces aren't. - inputWindowHandle.setTrustedOverlay(mInputTransaction, - w.mWinAnimator.mSurfaceController.mSurfaceControl, - w.isWindowTrustedOverlay()); populateInputWindowHandle(inputWindowHandle, w); setInputWindowInfoIfNeeded(mInputTransaction, w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 02f5c217e5d8..cd114fcf9e21 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -68,6 +68,7 @@ class InsetsSourceProvider { private final Rect mTmpRect = new Rect(); private final InsetsStateController mStateController; private final InsetsSourceControl mFakeControl; + private final Consumer<Transaction> mSetLeashPositionConsumer; private @Nullable InsetsSourceControl mControl; private @Nullable InsetsControlTarget mControlTarget; private @Nullable InsetsControlTarget mPendingControlTarget; @@ -85,16 +86,7 @@ class InsetsSourceProvider { private boolean mInsetsHintStale = true; private @Flags int mFlagsFromFrameProvider; private @Flags int mFlagsFromServer; - - private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { - if (mControl != null) { - final SurfaceControl leash = mControl.getLeash(); - if (leash != null) { - final Point position = mControl.getSurfacePosition(); - t.setPosition(leash, position.x, position.y); - } - } - }; + private boolean mHasPendingPosition; /** The visibility override from the current controlling window. */ private boolean mClientVisible; @@ -129,6 +121,21 @@ class InsetsSourceProvider { source.getId(), source.getType(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE); mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0; + mSetLeashPositionConsumer = t -> { + if (mControl != null) { + final SurfaceControl leash = mControl.getLeash(); + if (leash != null) { + final Point position = mControl.getSurfacePosition(); + t.setPosition(leash, position.x, position.y); + } + } + if (mHasPendingPosition) { + mHasPendingPosition = false; + if (mPendingControlTarget != mControlTarget) { + mStateController.notifyControlTargetChanged(mPendingControlTarget, this); + } + } + }; } InsetsSource getSource() { @@ -185,9 +192,8 @@ class InsetsSourceProvider { mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this); if (mControllable) { mWindowContainer.setControllableInsetProvider(this); - if (mPendingControlTarget != null) { + if (mPendingControlTarget != mControlTarget) { updateControlForTarget(mPendingControlTarget, true /* force */); - mPendingControlTarget = null; } } } @@ -344,6 +350,7 @@ class InsetsSourceProvider { changed = true; if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { + mHasPendingPosition = true; windowState.applyWithNextDraw(mSetLeashPositionConsumer); } else { Transaction t = mWindowContainer.getSyncTransaction(); @@ -465,18 +472,23 @@ class InsetsSourceProvider { // to control the window for now. return; } + mPendingControlTarget = target; if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) { // if window doesn't have a surface, set it null and return. setWindowContainer(null, null, null); } if (mWindowContainer == null) { - mPendingControlTarget = target; return; } if (target == mControlTarget && !force) { return; } + if (mHasPendingPosition) { + // Don't create a new leash while having a pending position. Otherwise, the position + // will be changed earlier than expected, which can cause flicker. + return; + } if (target == null) { // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields. mWindowContainer.cancelAnimation(); @@ -618,6 +630,7 @@ class InsetsSourceProvider { } pw.print(prefix); pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); + pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition); pw.println(); if (mWindowContainer != null) { pw.print(prefix + "mWindowContainer="); @@ -631,7 +644,7 @@ class InsetsSourceProvider { pw.print(prefix + "mControlTarget="); pw.println(mControlTarget); } - if (mPendingControlTarget != null) { + if (mPendingControlTarget != mControlTarget) { pw.print(prefix + "mPendingControlTarget="); pw.println(mPendingControlTarget); } @@ -652,7 +665,8 @@ class InsetsSourceProvider { if (mControlTarget != null && mControlTarget.getWindow() != null) { mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel); } - if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) { + if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget + && mPendingControlTarget.getWindow() != null) { mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel); } if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 081ebe0e7cbd..c4d01291f558 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -278,6 +278,12 @@ class InsetsStateController { notifyPendingInsetsControlChanged(); } + void notifyControlTargetChanged(@Nullable InsetsControlTarget target, + InsetsSourceProvider provider) { + onControlTargetChanged(provider, target, false /* fake */); + notifyPendingInsetsControlChanged(); + } + void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider) { removeFromControlMaps(previousControlTarget, provider, false /* fake */); diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java new file mode 100644 index 000000000000..3265e605ab15 --- /dev/null +++ b/services/core/java/com/android/server/wm/LegacyDimmer.java @@ -0,0 +1,348 @@ +/* + * 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.wm; + +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +public class LegacyDimmer extends Dimmer { + private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; + // This is in milliseconds. + private static final int DEFAULT_DIM_ANIM_DURATION = 200; + DimState mDimState; + private WindowContainer mLastRequestedDimContainer; + private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; + + private class DimAnimatable implements SurfaceAnimator.Animatable { + private SurfaceControl mDimLayer; + + private DimAnimatable(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + } + + @Override + public SurfaceControl.Transaction getSyncTransaction() { + return mHost.getSyncTransaction(); + } + + @Override + public SurfaceControl.Transaction getPendingTransaction() { + return mHost.getPendingTransaction(); + } + + @Override + public void commitPendingTransaction() { + mHost.commitPendingTransaction(); + } + + @Override + public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { + } + + @Override + public void onAnimationLeashLost(SurfaceControl.Transaction t) { + } + + @Override + public SurfaceControl.Builder makeAnimationLeash() { + return mHost.makeAnimationLeash(); + } + + @Override + public SurfaceControl getAnimationLeashParent() { + return mHost.getSurfaceControl(); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mDimLayer; + } + + @Override + public SurfaceControl getParentSurfaceControl() { + return mHost.getSurfaceControl(); + } + + @Override + public int getSurfaceWidth() { + // This will determine the size of the leash created. This should be the size of the + // host and not the dim layer since the dim layer may get bigger during animation. If + // that occurs, the leash size cannot change so we need to ensure the leash is big + // enough that the dim layer can grow. + // This works because the mHost will be a Task which has the display bounds. + return mHost.getSurfaceWidth(); + } + + @Override + public int getSurfaceHeight() { + // See getSurfaceWidth() above for explanation. + return mHost.getSurfaceHeight(); + } + + void removeSurface() { + if (mDimLayer != null && mDimLayer.isValid()) { + getSyncTransaction().remove(mDimLayer); + } + mDimLayer = null; + } + } + + @VisibleForTesting + class DimState { + /** + * The layer where property changes should be invoked on. + */ + SurfaceControl mDimLayer; + boolean mDimming; + boolean mIsVisible; + + // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. + final Rect mDimBounds = new Rect(); + + /** + * Determines whether the dim layer should animate before destroying. + */ + boolean mAnimateExit = true; + + /** + * Used for Dims not associated with a WindowContainer. + * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim + * lifecycle. + */ + boolean mDontReset; + SurfaceAnimator mSurfaceAnimator; + + DimState(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + mDimming = true; + final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); + mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { + if (!mDimming) { + dimAnimatable.removeSurface(); + } + }, mHost.mWmService); + } + } + + @VisibleForTesting + interface SurfaceAnimatorStarter { + void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, + AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type); + } + + protected LegacyDimmer(WindowContainer host) { + this(host, SurfaceAnimator::startAnimation); + } + + LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { + super(host); + mSurfaceAnimatorStarter = surfaceAnimatorStarter; + } + + private DimState obtainDimState(WindowContainer container) { + if (mDimState == null) { + try { + final SurfaceControl ctl = makeDimLayer(); + mDimState = new DimState(ctl); + } catch (Surface.OutOfResourcesException e) { + Log.w(TAG, "OutOfResourcesException creating dim surface"); + } + } + + mLastRequestedDimContainer = container; + return mDimState; + } + + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer() + .setName("Dim Layer for - " + mHost.getName()) + .setCallsite("Dimmer.makeDimLayer") + .build(); + } + + @Override + SurfaceControl getDimLayer() { + return mDimState != null ? mDimState.mDimLayer : null; + } + + @Override + void resetDimStates() { + if (mDimState == null) { + return; + } + if (!mDimState.mDontReset) { + mDimState.mDimming = false; + } + } + + @Override + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; + } + + @Override + void dontAnimateExit() { + if (mDimState != null) { + mDimState.mAnimateExit = false; + } + } + + @Override + protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) { + final DimState d = obtainDimState(container); + if (d == null) { + return; + } + + // The dim method is called from WindowState.prepareSurfaces(), which is always called + // in the correct Z from lowest Z to highest. This ensures that the dim layer is always + // relative to the highest Z layer with a dim. + SurfaceControl.Transaction t = mHost.getPendingTransaction(); + t.setAlpha(d.mDimLayer, alpha); + t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); + d.mDimming = true; + } + + @Override + protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) { + final DimState d = mDimState; + if (d != null) { + SurfaceControl.Transaction t = mHost.getPendingTransaction(); + t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); + } + } + + @Override + boolean updateDims(SurfaceControl.Transaction t) { + if (mDimState == null) { + return false; + } + + if (!mDimState.mDimming) { + if (!mDimState.mAnimateExit) { + if (mDimState.mDimLayer.isValid()) { + t.remove(mDimState.mDimLayer); + } + } else { + startDimExit(mLastRequestedDimContainer, + mDimState.mSurfaceAnimator, t); + } + mDimState = null; + return false; + } else { + final Rect bounds = mDimState.mDimBounds; + // TODO: Once we use geometry from hierarchy this falls away. + t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); + t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); + if (!mDimState.mIsVisible) { + mDimState.mIsVisible = true; + t.show(mDimState.mDimLayer); + // Skip enter animation while starting window is on top of its activity + final WindowState ws = mLastRequestedDimContainer.asWindowState(); + if (ws == null || ws.mActivityRecord == null + || ws.mActivityRecord.mStartingData == null) { + startDimEnter(mLastRequestedDimContainer, + mDimState.mSurfaceAnimator, t); + } + } + return true; + } + } + + private long getDimDuration(WindowContainer container) { + // Use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) + : animationAdapter.getDurationHint(); + } + + private void startDimEnter(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); + } + + private void startDimExit(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); + } + + private void startAnim(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t, float startAlpha, float endAlpha) { + mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( + new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), + mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, + ANIMATION_TYPE_DIMMER); + } + + private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private final long mDuration; + private final float mFromAlpha; + private final float mToAlpha; + + AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { + mFromAlpha = fromAlpha; + mToAlpha = toAlpha; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + final float fraction = getFraction(currentPlayTime); + final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; + t.setAlpha(sc, alpha); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); + pw.print(" to="); pw.print(mToAlpha); + pw.print(" duration="); pw.println(mDuration); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ALPHA); + proto.write(FROM, mFromAlpha); + proto.write(TO, mToAlpha); + proto.write(DURATION_MS, mDuration); + proto.end(token); + } + } +} diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 458786ffdbc0..f8c39d0906a0 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -16,6 +16,7 @@ lihongyu@google.com mariiasand@google.com rgl@google.com yunfanc@google.com +wilsonshih@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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index cf6a1feef5ee..7a442e708130 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2211,6 +2211,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( organizedTf); } + + if (taskDisplayArea.getFocusedRootTask() == rootTask) { + taskDisplayArea.clearPreferredTopFocusableRootTask(); + } } finally { mService.continueWindowLayout(); try { diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java new file mode 100644 index 000000000000..2549bbf70e9f --- /dev/null +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -0,0 +1,413 @@ +/* + * 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.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER; +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; + +import java.io.PrintWriter; + +class SmoothDimmer extends Dimmer { + private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; + private static final float EPSILON = 0.0001f; + // This is in milliseconds. + private static final int DEFAULT_DIM_ANIM_DURATION = 200; + DimState mDimState; + private WindowContainer mLastRequestedDimContainer; + private final AnimationAdapterFactory mAnimationAdapterFactory; + + @VisibleForTesting + class DimState { + /** + * The layer where property changes should be invoked on. + */ + SurfaceControl mDimLayer; + boolean mDimming; + boolean mIsVisible; + + // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. + final Rect mDimBounds = new Rect(); + + /** + * Determines whether the dim layer should animate before destroying. + */ + boolean mAnimateExit = true; + + /** + * Used for Dims not associated with a WindowContainer. + * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim + * lifecycle. + */ + boolean mDontReset; + + Change mCurrentProperties; + Change mRequestedProperties; + private AnimationSpec mAlphaAnimationSpec; + private AnimationAdapter mLocalAnimationAdapter; + + static class Change { + private float mAlpha = -1f; + private int mBlurRadius = -1; + private WindowContainer mDimmingContainer = null; + private int mRelativeLayer = -1; + private boolean mSkipAnimation = false; + + Change() {} + + Change(Change other) { + mAlpha = other.mAlpha; + mBlurRadius = other.mBlurRadius; + mDimmingContainer = other.mDimmingContainer; + mRelativeLayer = other.mRelativeLayer; + } + + @Override + public String toString() { + return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container=" + + mDimmingContainer + ", relativePosition=" + mRelativeLayer + + ", skipAnimation=" + mSkipAnimation; + } + } + + DimState(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + mDimming = true; + mCurrentProperties = new Change(); + mRequestedProperties = new Change(); + } + + void setExitParameters(WindowContainer container) { + setRequestedRelativeParent(container, -1 /* relativeLayer */); + setRequestedAppearance(0f /* alpha */, 0 /* blur */); + } + + // Sets a requested change without applying it immediately + void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) { + mRequestedProperties.mDimmingContainer = relativeParent; + mRequestedProperties.mRelativeLayer = relativeLayer; + } + + // Sets a requested change without applying it immediately + void setRequestedAppearance(float alpha, int blurRadius) { + mRequestedProperties.mAlpha = alpha; + mRequestedProperties.mBlurRadius = blurRadius; + } + + /** + * Commit the last changes we received. Called after + * {@link Change#setExitParameters(WindowContainer)}, + * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or + * {@link Change#setRequestedAppearance(float, int)} + */ + void applyChanges(SurfaceControl.Transaction t) { + if (mRequestedProperties.mDimmingContainer == null) { + Log.e(TAG, this + " does not have a dimming container. Have you forgotten to " + + "call adjustRelativeLayer?"); + return; + } + if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { + Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer + + "does not have a surface"); + return; + } + if (!mDimState.mIsVisible) { + mDimState.mIsVisible = true; + t.show(mDimState.mDimLayer); + } + t.setRelativeLayer(mDimLayer, + mRequestedProperties.mDimmingContainer.getSurfaceControl(), + mRequestedProperties.mRelativeLayer); + + if (aspectChanged()) { + if (isAnimating()) { + mLocalAnimationAdapter.onAnimationCancelled(mDimLayer); + } + if (mRequestedProperties.mSkipAnimation + || (!dimmingContainerChanged() && mDimming)) { + // If the dimming container has not changed, then it is running its own + // animation, thus we can directly set the values we get requested, unless it's + // the exiting animation + ProtoLog.d(WM_DEBUG_DIMMER, + "Dim %s skipping animation and directly setting alpha=%f, blur=%d", + mDimLayer, mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius); + t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); + t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); + mRequestedProperties.mSkipAnimation = false; + } else { + startAnimation(t); + } + } + mCurrentProperties = new Change(mRequestedProperties); + } + + private void startAnimation(SurfaceControl.Transaction t) { + mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius); + mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec, + mHost.mWmService.mSurfaceAnimationRunner); + + mLocalAnimationAdapter.startAnimation(mDimLayer, t, + ANIMATION_TYPE_DIMMER, (type, animator) -> { + t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); + t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); + if (mRequestedProperties.mAlpha == 0f && !mDimming) { + ProtoLog.d(WM_DEBUG_DIMMER, + "Removing dim surface %s on transaction %s", mDimLayer, t); + t.remove(mDimLayer); + } + mLocalAnimationAdapter = null; + mAlphaAnimationSpec = null; + }); + } + + private boolean isAnimating() { + return mAlphaAnimationSpec != null; + } + + private boolean aspectChanged() { + return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON + || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius; + } + + private boolean dimmingContainerChanged() { + return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer; + } + + private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) { + final float startAlpha; + final int startBlur; + if (mAlphaAnimationSpec != null) { + startAlpha = mAlphaAnimationSpec.mCurrentAlpha; + startBlur = mAlphaAnimationSpec.mCurrentBlur; + } else { + startAlpha = Math.max(mCurrentProperties.mAlpha, 0f); + startBlur = Math.max(mCurrentProperties.mBlurRadius, 0); + } + long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer) + * Math.abs(targetAlpha - startAlpha)); + + ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, " + + "alpha: %f -> %f, blur: %d -> %d", + mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha, + startBlur, targetBlur); + return new AnimationSpec( + new AnimationExtremes<>(startAlpha, targetAlpha), + new AnimationExtremes<>(startBlur, targetBlur), + duration + ); + } + } + + protected SmoothDimmer(WindowContainer host) { + this(host, new AnimationAdapterFactory()); + } + + @VisibleForTesting + SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) { + super(host); + mAnimationAdapterFactory = animationFactory; + } + + private DimState obtainDimState(WindowContainer container) { + if (mDimState == null) { + try { + final SurfaceControl ctl = makeDimLayer(); + mDimState = new DimState(ctl); + } catch (Surface.OutOfResourcesException e) { + Log.w(TAG, "OutOfResourcesException creating dim surface"); + } + } + + mLastRequestedDimContainer = container; + return mDimState; + } + + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer() + .setName("Dim Layer for - " + mHost.getName()) + .setCallsite("Dimmer.makeDimLayer") + .build(); + } + + @Override + SurfaceControl getDimLayer() { + return mDimState != null ? mDimState.mDimLayer : null; + } + + @Override + void resetDimStates() { + if (mDimState == null) { + return; + } + if (!mDimState.mDontReset) { + mDimState.mDimming = false; + } + } + + @Override + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; + } + + @Override + void dontAnimateExit() { + if (mDimState != null) { + mDimState.mAnimateExit = false; + } + } + + @Override + protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) { + final DimState d = obtainDimState(container); + mDimState.setRequestedAppearance(alpha, blurRadius); + d.mDimming = true; + } + + @Override + protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) { + if (mDimState != null) { + mDimState.setRequestedRelativeParent(container, relativeLayer); + } + } + + boolean updateDims(SurfaceControl.Transaction t) { + if (mDimState == null) { + return false; + } + + if (!mDimState.mDimming) { + // No one is dimming anymore, fade out dim and remove + if (!mDimState.mAnimateExit) { + if (mDimState.mDimLayer.isValid()) { + t.remove(mDimState.mDimLayer); + } + } else { + mDimState.setExitParameters( + mDimState.mRequestedProperties.mDimmingContainer); + mDimState.applyChanges(t); + } + mDimState = null; + return false; + } + final Rect bounds = mDimState.mDimBounds; + // TODO: Once we use geometry from hierarchy this falls away. + t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); + t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); + // Skip enter animation while starting window is on top of its activity + final WindowState ws = mLastRequestedDimContainer.asWindowState(); + if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null + && ws.mActivityRecord.mStartingData != null) { + mDimState.mRequestedProperties.mSkipAnimation = true; + } + mDimState.applyChanges(t); + return true; + } + + private long getDimDuration(WindowContainer container) { + // Use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) + : animationAdapter.getDurationHint(); + } + + private static class AnimationExtremes<T> { + final T mStartValue; + final T mFinishValue; + + AnimationExtremes(T fromValue, T toValue) { + mStartValue = fromValue; + mFinishValue = toValue; + } + } + + private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private final long mDuration; + private final AnimationExtremes<Float> mAlpha; + private final AnimationExtremes<Integer> mBlur; + + float mCurrentAlpha = 0; + int mCurrentBlur = 0; + + AnimationSpec(AnimationExtremes<Float> alpha, + AnimationExtremes<Integer> blur, long duration) { + mAlpha = alpha; + mBlur = blur; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + final float fraction = getFraction(currentPlayTime); + mCurrentAlpha = + fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue; + mCurrentBlur = + (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue; + t.setAlpha(sc, mCurrentAlpha); + t.setBackgroundBlurRadius(sc, mCurrentBlur); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue); + pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue); + pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue); + pw.print(" to_blur="); pw.print(mBlur.mFinishValue); + pw.print(" duration="); pw.println(mDuration); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ALPHA); + proto.write(FROM, mAlpha.mStartValue); + proto.write(TO, mAlpha.mFinishValue); + proto.write(DURATION_MS, mDuration); + proto.end(token); + } + } + + static class AnimationAdapterFactory { + + public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, + SurfaceAnimationRunner runner) { + return new LocalAnimationAdapter(alphaAnimationSpec, runner); + } + } +} diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 408ea6eb8c1a..c3de4d5acc21 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -49,7 +49,8 @@ import java.util.function.Supplier; * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the * animation will be invoked, at which we reparent the children back to the original parent. */ -class SurfaceAnimator { +@VisibleForTesting +public class SurfaceAnimator { private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM; @@ -617,7 +618,8 @@ class SurfaceAnimator { * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the * component that is running the animation when the animation is finished. */ - interface OnAnimationFinishedCallback { + @VisibleForTesting + public interface OnAnimationFinishedCallback { void onAnimationFinished(@AnimationType int type, AnimationAdapter anim); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 83856153a709..4922e9028ed9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -199,6 +199,7 @@ import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService; import com.android.server.am.AppTimeTracker; import com.android.server.uri.NeededUriGrants; +import com.android.window.flags.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -488,10 +489,6 @@ class Task extends TaskFragment { private boolean mForceShowForAllUsers; - /** When set, will force the task to report as invisible. */ - static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; - static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; - private int mForceHiddenFlags = 0; private boolean mForceTranslucent = false; // The display category name for this task. @@ -2858,7 +2855,8 @@ class Task extends TaskFragment { } /** Bounds of the task to be used for dimming, as well as touch related tests. */ - void getDimBounds(Rect out) { + @Override + void getDimBounds(@NonNull Rect out) { if (isRootTask()) { getBounds(out); return; @@ -3306,7 +3304,8 @@ class Task extends TaskFragment { // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}. // If true, we want to get the Dimmer from the level above since we don't want to animate // the dim with the Task. - if (!isRootTask() || isTranslucent(null)) { + if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible()) + || isTranslucent(null)) { return super.getDimmer(); } @@ -4492,20 +4491,13 @@ class Task extends TaskFragment { * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. * @return Whether the force hidden state changed */ - boolean setForceHidden(int flags, boolean set) { - int newFlags = mForceHiddenFlags; - if (set) { - newFlags |= flags; - } else { - newFlags &= ~flags; - } - if (mForceHiddenFlags == newFlags) { - return false; - } - + @Override + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { final boolean wasHidden = isForceHidden(); final boolean wasVisible = isVisible(); - mForceHiddenFlags = newFlags; + if (!super.setForceHidden(flags, set)) { + return false; + } final boolean nowHidden = isForceHidden(); if (wasHidden != nowHidden) { final String reason = "setForceHidden"; @@ -4536,11 +4528,6 @@ class Task extends TaskFragment { return super.isAlwaysOnTop(); } - @Override - protected boolean isForceHidden() { - return mForceHiddenFlags != 0; - } - boolean isForceHiddenForPinnedTask() { return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0; } @@ -5648,6 +5635,8 @@ class Task extends TaskFragment { if (noAnimation) { mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(top); + mTransitionController.collect(top); + mTransitionController.setNoAnimation(top); ActivityOptions.abort(options); } else { updateTransitLocked(TRANSIT_TO_FRONT, options); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 50bc825d8bea..906b3b55e015 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.HostingRecord; import com.android.server.pm.pkg.AndroidPackage; +import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -209,7 +210,26 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ int mMinHeight; - Dimmer mDimmer = new Dimmer(this); + Dimmer mDimmer = Flags.dimmerRefactor() + ? new SmoothDimmer(this) : new LegacyDimmer(this); + + /** Apply the dim layer on the embedded TaskFragment. */ + static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0; + + /** Apply the dim layer on the parent Task for an embedded TaskFragment. */ + static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1; + + /** + * The type of dim layer area for an embedded TaskFragment. + */ + @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = { + EMBEDDED_DIM_AREA_TASK_FRAGMENT, + EMBEDDED_DIM_AREA_PARENT_TASK, + }) + @interface EmbeddedDimArea {} + + @EmbeddedDimArea + private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT; /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; @@ -342,6 +362,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mIsolatedNav; + /** When set, will force the task to report as invisible. */ + static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2; + + @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = { + FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, + FLAG_FORCE_HIDDEN_FOR_TASK_ORG, + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, + }, flag = true) + @interface FlagForceHidden {} + protected int mForceHiddenFlags = 0; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -825,7 +858,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { * Returns whether this TaskFragment is currently forced to be hidden for any reason. */ protected boolean isForceHidden() { - return false; + return mForceHiddenFlags != 0; + } + + /** + * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. + * @return Whether the force hidden state changed + */ + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { + int newFlags = mForceHiddenFlags; + if (set) { + newFlags |= flags; + } else { + newFlags &= ~flags; + } + if (mForceHiddenFlags == newFlags) { + return false; + } + mForceHiddenFlags = newFlags; + return true; } protected boolean isForceTranslucent() { @@ -969,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // A TaskFragment isn't translucent if it has at least one visible activity that occludes // this TaskFragment. return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, - starting) == null; + starting, true /* ignoringKeyguard */) == null; } /** @@ -982,7 +1033,20 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // Including finishing Activity if the TaskFragment is becoming invisible in the transition. - return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; + return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this, + true /* ignoringKeyguard */) == null; + } + + /** + * Like {@link #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the + * windows rather than their visibility ignoring keyguard. + */ + boolean isTranslucentAndVisible() { + if (!isAttached() || isForceHidden() || isForceTranslucent()) { + return true; + } + return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null, + false /* ignoringKeyguard */) == null; } ActivityRecord getTopNonFinishingActivity() { @@ -2929,14 +2993,27 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Override Dimmer getDimmer() { - // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment. - if (asTask() == null) { + // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment. + if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) { return mDimmer; } return super.getDimmer(); } + /** Bounds to be used for dimming, as well as touch related tests. */ + void getDimBounds(@NonNull Rect out) { + if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) { + out.set(getTask().getBounds()); + } else { + out.set(getBounds()); + } + } + + void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) { + mEmbeddedDimArea = embeddedDimArea; + } + @Override void prepareSurfaces() { if (asTask() != null) { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 04164c20a372..ff766beee337 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,7 +51,6 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; -import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -745,9 +744,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { + boolean isSystemOrganizer(@NonNull IBinder organizerToken) { final TaskFragmentOrganizerState state = - mTaskFragmentOrganizerState.get(token.asBinder()); + mTaskFragmentOrganizerState.get(organizerToken); return state != null && state.mIsSystemOrganizer; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f3fb7c442b78..7d65c61193b5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -418,8 +418,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (transientRoot == null) continue; final WindowContainer<?> rootParent = transientRoot.getParent(); if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; - final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor - .mOpaqueActivityHelper.getOpaqueActivity(rootParent); + final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper + .getOpaqueActivity(rootParent, true /* ignoringKeyguard */); if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { occludedCount++; } @@ -3321,8 +3321,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFrozen.add(wc); final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); changeInfo.mSnapshot = snapshotSurface; - if (isDisplayRotation) { - // This isn't cheap, so only do it for display rotations. + if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { + // This isn't cheap, so only do it for rotation change. changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( buffer, screenshotBuffer.getColorSpace()); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f509463c5409..8ac21e41f7f4 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -757,7 +757,7 @@ class TransitionController { final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType, startTaskInfo, pipTaskInfo, remoteTransition, displayChange, - transition.getFlags()); + transition.getFlags(), transition.getSyncId()); transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); transition.mLogger.mRequest = request; @@ -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) { @@ -1584,8 +1592,8 @@ class TransitionController { TransitionInfo mInfo; private String buildOnSendLog() { - StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId) - .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); + StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId) + .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); if (mRequest != null) { sb.append(" via request=").append(mRequest); } @@ -1609,7 +1617,8 @@ class TransitionController { void logOnSend() { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog()); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", mInfo); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", + mInfo.toString(" " /* prefix */)); } private static String toMsString(long nanos) { @@ -1617,8 +1626,8 @@ class TransitionController { } private String buildOnFinishLog() { - StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId) - .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); + StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId) + .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs)); if (mRequestTimeNs != 0) { sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs)); diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 00b9b4c490a4..5b9acb2f67c4 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f339d249e272..88f72f9dbc90 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2261,6 +2261,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + final boolean wasTrustedOverlay = win.isWindowTrustedOverlay(); flagChanges = win.mAttrs.flags ^ attrs.flags; privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags; attrChanges = win.mAttrs.copyFrom(attrs); @@ -2273,6 +2274,9 @@ public class WindowManagerService extends IWindowManager.Stub if (layoutChanged && win.providesDisplayDecorInsets()) { configChanged = displayPolicy.updateDecorInsetsInfo(); } + if (wasTrustedOverlay != win.isWindowTrustedOverlay()) { + win.updateTrustedOverlay(); + } if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0 || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) { win.mActivityRecord.checkKeyguardFlagsChanged(); @@ -5299,7 +5303,11 @@ public class WindowManagerService extends IWindowManager.Stub public void displayReady() { synchronized (mGlobalLock) { if (mMaxUiWidth > 0) { - mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth)); + mRoot.forAllDisplays(dc -> { + if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) { + dc.setMaxUiWidth(mMaxUiWidth); + } + }); } applyForcedPropertiesForDefaultDisplay(); mAnimator.ready(); @@ -8453,16 +8461,18 @@ public class WindowManagerService extends IWindowManager.Stub return true; } // For a task session, find the activity identified by the launch cookie. - final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie( + final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie( incomingSession.getTokenToRecord()); - if (wct == null) { + if (wci == null) { Slog.w(TAG, "Handling a new recording session; unable to find the " + "WindowContainerToken"); return false; } // Replace the launch cookie in the session details with the task's // WindowContainerToken. - incomingSession.setTokenToRecord(wct.asBinder()); + incomingSession.setTokenToRecord(wci.getToken().asBinder()); + // Also replace the UNKNOWN target UID with the actual UID. + incomingSession.setTargetUid(wci.getUid()); mContentRecordingController.setContentRecordingSessionLocked(incomingSession, WindowManagerService.this); return true; @@ -8790,21 +8800,41 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.setFocusedTask(task.mTaskId, touchedActivity); } + @VisibleForTesting + static class WindowContainerInfo { + private final int mUid; + @NonNull private final WindowContainerToken mToken; + + private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) { + this.mUid = uid; + this.mToken = token; + } + + public int getUid() { + return mUid; + } + + @NonNull + public WindowContainerToken getToken() { + return mToken; + } + } + /** - * Retrieve the {@link WindowContainerToken} of the task that contains the activity started - * with the given launch cookie. + * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with + * the given launch cookie. * * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an - * activity + * activity * @return a token representing the task containing the activity started with the given launch - * cookie, or {@code null} if the token couldn't be found. + * cookie, or {@code null} if the token couldn't be found. */ @VisibleForTesting @Nullable - WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) { + WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) { // Find the activity identified by the launch cookie. - final ActivityRecord targetActivity = mRoot.getActivity( - activity -> activity.mLaunchCookie == launchCookie); + final ActivityRecord targetActivity = + mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie); if (targetActivity == null) { Slog.w(TAG, "Unable to find the activity for this launch cookie"); return null; @@ -8819,7 +8849,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName()); return null; } - return taskWindowContainerToken; + return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dd9a88f72bde..5ed6caffe1fb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -24,7 +24,9 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -34,6 +36,8 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATI import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; +import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN; import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION; @@ -61,6 +65,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -821,6 +826,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return TRANSACT_EFFECTS_NONE; } + int effects = TRANSACT_EFFECTS_NONE; // When the TaskFragment is resized, we may want to create a change transition for it, for // which we want to defer the surface update until we determine whether or not to start // change transition. @@ -843,7 +849,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub c.getConfiguration().windowConfiguration.setBounds(absBounds); taskFragment.setRelativeEmbeddedBounds(relBounds); } - final int effects = applyChanges(taskFragment, c); + if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { + if (taskFragment.setForceHidden( + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + effects |= applyChanges(taskFragment, c); + if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { taskFragment.initializeChangeTransition(mTmpBounds0); } @@ -1393,6 +1406,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub taskFragment.setIsolatedNav(isolatedNav); break; } + case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(0, taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } + case OP_TYPE_REORDER_TO_TOP_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } } return effects; } @@ -1420,6 +1451,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK + || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or " + + "OP_TYPE_REORDER_TO_TOP_OF_TASK." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, @@ -1920,6 +1963,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * For config change on {@link TaskFragment}, we only support the following operations: * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)}, * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}. + * + * For a system organizer, we additionally support + * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and + * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See + * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)} */ private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func, @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change, @@ -1938,31 +1986,49 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new SecurityException(msg); } - final int changeMask = change.getChangeMask(); - final int configSetMask = change.getConfigSetMask(); - final int windowSetMask = change.getWindowSetMask(); - if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0 - && change.getWindowingMode() >= 0) { - // The change contains only setWindowingMode, which is allowed. - return; + final int originalChangeMask = change.getChangeMask(); + final int originalConfigSetMask = change.getConfigSetMask(); + final int originalWindowSetMask = change.getWindowSetMask(); + + int changeMaskToBeChecked = originalChangeMask; + int configSetMaskToBeChecked = originalConfigSetMask; + int windowSetMaskToBeChecked = originalWindowSetMask; + + if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + // System organizer is allowed to update the hidden and focusable state. + // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. + changeMaskToBeChecked &= ~CHANGE_HIDDEN; + changeMaskToBeChecked &= ~CHANGE_FOCUSABLE; } - if (changeMask != CHANGE_RELATIVE_BOUNDS - || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION - || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) { - // None of the change should be requested from a TaskFragment organizer except - // setRelativeBounds and setWindowingMode. - // For setRelativeBounds, we don't need to check whether it is outside of the Task + + // setRelativeBounds is allowed. + if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0 + && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { + // For setRelativeBounds, we don't need to check whether it is outside the Task // bounds, because it is possible that the Task is also resizing, for which we don't // want to throw an exception. The bounds will be adjusted in // TaskFragment#translateRelativeBoundsToAbsoluteBounds. - String msg = "Permission Denial: " + func + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " trying to apply changes of changeMask=" + changeMask - + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask - + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; - Slog.w(TAG, msg); - throw new SecurityException(msg); + changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS; + configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS; } + + if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0 + && windowSetMaskToBeChecked == 0) { + // All the changes have been checked. + // Note that setWindowingMode is always allowed, so we don't need to check the mask. + return; + } + + final String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply changes of changeMask=" + originalChangeMask + + " configSetMask=" + originalConfigSetMask + + " windowSetMask=" + originalWindowSetMask + + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); } private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -2019,7 +2085,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); taskFragment.setTaskFragmentOrganizer(organizerToken, ownerActivity.getUid(), ownerActivity.info.processName, - mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); + mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())); final int position; if (creationParams.getPairedPrimaryFragmentToken() != null) { // When there is a paired primary TaskFragment, we want to place the new TaskFragment diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4beec2bc79e6..3a793e921c68 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; @@ -1188,7 +1189,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - public boolean isWindowTrustedOverlay() { + @Override + void setInitialSurfaceControlProperties(SurfaceControl.Builder b) { + super.setInitialSurfaceControlProperties(b); + if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) { + getPendingTransaction().setTrustedOverlay(mSurfaceControl, true); + } + } + + void updateTrustedOverlay() { + mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl, + isWindowTrustedOverlay()); + } + + boolean isWindowTrustedOverlay() { return InputMonitor.isTrustedOverlay(mAttrs.type) || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0 && mSession.mCanAddInternalSystemWindow) @@ -2755,12 +2769,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // bounds, as they would be used to display the dim layer. final TaskFragment taskFragment = getTaskFragment(); if (taskFragment != null) { - final Task task = taskFragment.asTask(); - if (task != null) { - task.getDimBounds(mTmpRect); - } else { - mTmpRect.set(taskFragment.getBounds()); - } + taskFragment.getDimBounds(mTmpRect); } else if (getRootTask() != null) { getRootTask().getDimBounds(mTmpRect); } @@ -3732,30 +3741,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(); } @@ -5113,8 +5136,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void applyDims() { if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind()) - && mToken.isVisibleRequested() && isVisibleNow() && !mHidden - && mTransitionController.canApplyDim(getTask())) { + && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow()) + && !mHidden && mTransitionController.canApplyDim(getTask())) { // Only show the Dimmer when the following is satisfied: // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting. @@ -5124,7 +5147,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mIsDimming = true; final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0; final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0; - getDimmer().dimBelow(this, dimAmount, blurRadius); + // If the window is visible from surface flinger perspective (mWinAnimator.getShown()) + // but not window manager visible (!isVisibleNow()), it can still be the parent of the + // dim, but can not create a new surface or continue a dim alone. + if (isVisibleNow()) { + getDimmer().adjustAppearance(this, dimAmount, blurRadius); + } + getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */); } } @@ -5184,14 +5213,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void prepareSurfaces() { mIsDimming = false; if (mHasSurface) { - applyDims(); + if (!Dimmer.DIMMER_REFACTOR) { + applyDims(); + } updateSurfacePositionNonOrganized(); // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); updateScaleIfNeeded(); mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); - if (surfaceTrustedOverlay()) { - getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay()); + if (Dimmer.DIMMER_REFACTOR) { + applyDims(); } } super.prepareSurfaces(); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e434f296bbb5..7d21dbf85a66 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -569,6 +569,7 @@ class WindowToken extends WindowContainer<WindowState> { && asActivityRecord() != null && isVisible()) { // Trigger an activity level rotation transition. mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this); + mTransitionController.collectVisibleChange(this); mTransitionController.setReady(this); } final int originalRotation = getWindowConfiguration().getRotation(); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index bc70658a06c4..24ee16389fd2 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", @@ -189,7 +189,7 @@ cc_defaults { "android.hardware.thermal@1.0", "android.hardware.thermal-V1-ndk", "android.hardware.tv.input@1.0", - "android.hardware.tv.input-V1-ndk", + "android.hardware.tv.input-V2-ndk", "android.hardware.vibrator-V2-cpp", "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", @@ -244,5 +244,5 @@ filegroup { filegroup { name: "lib_oomConnection_native", - srcs: ["com_android_server_am_OomConnection.cpp",], + srcs: ["com_android_server_am_OomConnection.cpp"], } diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 7e8ce60b8a50..0e45f61cbfe1 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -20,6 +20,7 @@ per-file com_android_server_lights_* = file:/services/core/java/com/android/serv per-file com_android_server_location_* = file:/location/java/android/location/OWNERS per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS +per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp deleted file mode 100644 index 97e69fb4bb40..000000000000 --- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#include <android_runtime/AndroidRuntime.h> -#include <nativehelper/JNIHelp.h> -#include <jni.h> -#include <nativehelper/ScopedUtfChars.h> - -#include <utils/misc.h> -#include <sys/ioctl.h> -#include <sys/mount.h> -#include <utils/Log.h> - - -#include <inttypes.h> -#include <fcntl.h> -#include <errno.h> -#include <string.h> - -namespace android { - - uint64_t get_block_device_size(int fd) - { - uint64_t size = 0; - int ret; - - ret = ioctl(fd, BLKGETSIZE64, &size); - - if (ret) - return 0; - - return size; - } - - int wipe_block_device(int fd) - { - uint64_t range[2]; - int ret; - uint64_t len = get_block_device_size(fd); - - range[0] = 0; - range[1] = len; - - if (range[1] == 0) - return 0; - - ret = ioctl(fd, BLKSECDISCARD, &range); - if (ret < 0) { - ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); - range[0] = 0; - range[1] = len; - ret = ioctl(fd, BLKDISCARD, &range); - if (ret < 0) { - ALOGE("Discard failed: %s\n", strerror(errno)); - return -1; - } else { - ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); - return 0; - } - - } - - return ret; - } - - static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) - { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_RDONLY); - - if (fd < 0) - return 0; - - const uint64_t size = get_block_device_size(fd); - - close(fd); - - return size; - } - - static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_WRONLY); - - if (fd < 0) - return 0; - - const int ret = wipe_block_device(fd); - - close(fd); - - return ret; - } - - 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}, - }; - - int register_android_server_PersistentDataBlockService(JNIEnv* env) - { - return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService", - sMethods, NELEM(sMethods)); - } - -} /* namespace android */
\ No newline at end of file diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp index b256f168f2af..1844d3063cd8 100644 --- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -24,33 +24,33 @@ #include "utils/Log.h" namespace android { -static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds, jfloatArray jthresholds) { - if (juids == nullptr || jthresholds == nullptr) return; + if (jappIds == nullptr || jthresholds == nullptr) return; - ScopedIntArrayRO uids(env, juids); + ScopedIntArrayRO appIds(env, jappIds); ScopedFloatArrayRO thresholds(env, jthresholds); - if (uids.size() != thresholds.size()) { - ALOGE("uids size exceeds thresholds size!"); + if (appIds.size() != thresholds.size()) { + ALOGE("appIds size exceeds thresholds size!"); return; } - std::vector<int32_t> uidVector; + std::vector<int32_t> appIdVector; std::vector<float> thresholdVector; - size_t size = uids.size(); - uidVector.reserve(size); + size_t size = appIds.size(); + appIdVector.reserve(size); thresholdVector.reserve(size); for (int i = 0; i < size; i++) { - uidVector.push_back(static_cast<int32_t>(uids[i])); + appIdVector.push_back(static_cast<int32_t>(appIds[i])); thresholdVector.push_back(static_cast<float>(thresholds[i])); } - SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); + SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector); } -static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId, jfloat threshold) { - SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); + SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold); } static const JNINativeMethod gMethods[] = { diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp new file mode 100644 index 000000000000..1e3cfd049e65 --- /dev/null +++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <android_runtime/AndroidRuntime.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedUtfChars.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <utils/Log.h> +#include <utils/misc.h> + +namespace android { + +uint64_t get_block_device_size(int fd) { + uint64_t size = 0; + int ret; + + ret = ioctl(fd, BLKGETSIZE64, &size); + + if (ret) return 0; + + return size; +} + +int wipe_block_device(int fd) { + uint64_t range[2]; + int ret; + uint64_t len = get_block_device_size(fd); + + range[0] = 0; + range[1] = len; + + if (range[1] == 0) return 0; + + ret = ioctl(fd, BLKSECDISCARD, &range); + if (ret < 0) { + ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); + range[0] = 0; + range[1] = len; + ret = ioctl(fd, BLKDISCARD, &range); + if (ret < 0) { + ALOGE("Discard failed: %s\n", strerror(errno)); + return -1; + } else { + ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); + return 0; + } + } + + return ret; +} + +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); + + if (fd < 0) return 0; + + const uint64_t size = get_block_device_size(fd); + + close(fd); + + return size; +} + +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); + + if (fd < 0) return 0; + + const int ret = wipe_block_device(fd); + + close(fd); + + return ret; +} + +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"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_pdb_PersistentDataBlockService(JNIEnv *env) { + return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", + sMethods, NELEM(sMethods)); +} + +} /* namespace android */
\ No newline at end of file diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index df4489528be5..11734da5b1ac 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 68e2c9a57c24..dc05462c1a1c 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) { @@ -369,12 +368,20 @@ JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWr } JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper( - const AidlTvMessageEvent& aidlTvMessageEvent) { + const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) { + auto messageList = aidlTvMessageEvent.messages; TvMessageEventWrapper event; - event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1, - std::end(aidlTvMessageEvent.messages)); + // Handle backwards compatibility for V1 + if (isLegacyMessage) { + event.deviceId = messageList[0].groupId; + event.messages.insert(event.messages.begin(), std::begin(messageList) + 1, + std::end(messageList)); + } else { + event.deviceId = aidlTvMessageEvent.deviceId; + event.messages.insert(event.messages.begin(), std::begin(messageList), + std::end(messageList)); + } event.streamId = aidlTvMessageEvent.streamId; - event.deviceId = aidlTvMessageEvent.messages[0].groupId; event.type = aidlTvMessageEvent.type; return event; } @@ -450,15 +457,30 @@ JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) { ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent( const AidlTvMessageEvent& event) { const std::string DEVICE_ID_SUBTYPE = "device_id"; - if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) { - mHal->mLooper - ->sendMessage(new NotifyTvMessageHandler(mHal, - TvMessageEventWrapper::createEventWrapper( - event)), - static_cast<int>(event.type)); + ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok(); + int32_t aidlVersion = 0; + if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) { + bool validLegacyMessage = aidlVersion == 1 && + event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1; + bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0; + if (validLegacyMessage || validTvMessage) { + mHal->mLooper->sendMessage( + new NotifyTvMessageHandler(mHal, + TvMessageEventWrapper:: + createEventWrapper(event, + validLegacyMessage)), + static_cast<int>(event.type)); + } else { + status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion); + } + } else { + status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not " + "be inferred.", + aidlVersion); } - - return ::ndk::ScopedAStatus::ok(); + return status; } JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput) @@ -522,4 +544,12 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aid } } +::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) { + if (mIsHidl) { + return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } else { + return mAidlTvInput->getInterfaceVersion(_aidl_return); + } +} + } // namespace android diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index b7b4b1621512..6026a107c67f 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -138,7 +138,7 @@ private: TvMessageEventWrapper() {} static TvMessageEventWrapper createEventWrapper( - const AidlTvMessageEvent& aidlTvMessageEvent); + const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage); int streamId; int deviceId; @@ -195,6 +195,7 @@ private: ::ndk::ScopedAStatus getTvMessageQueueDesc( MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId, int32_t in_streamId); + ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return); private: ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback); @@ -220,7 +221,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 49af89b5f02e..5a620a3b87f5 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 59f1edcf309d..924e2f8ec654 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; @@ -337,6 +338,8 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.modes.ModeManagerService"; private static final String WEAR_DISPLAY_SERVICE_CLASS = "com.android.clockwork.display.WearDisplayService"; + private static final String WEAR_DEBUG_SERVICE_CLASS = + "com.android.clockwork.debug.WearDebugService"; private static final String WEAR_TIME_SERVICE_CLASS = "com.android.clockwork.time.WearTimeService"; private static final String WEAR_SETTINGS_SERVICE_CLASS = @@ -2635,6 +2638,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS); t.traceEnd(); + if (Build.IS_DEBUGGABLE) { + t.traceBegin("StartWearDebugService"); + mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS); + t.traceEnd(); + } + t.traceBegin("StartWearTimeService"); mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS); t.traceEnd(); diff --git a/services/midi/Android.bp b/services/midi/Android.bp index 5adcfbaf432e..4b5f8a7bf0ac 100644 --- a/services/midi/Android.bp +++ b/services/midi/Android.bp @@ -19,4 +19,7 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.midi-sources"], libs: ["services.core"], + static_libs: [ + "aconfig_midi_flags_java_lib", + ], } diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 486ddb4cb354..2f47cc7160b7 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -16,6 +16,8 @@ package com.android.server.midi; +import static com.android.media.midi.flags.Flags.virtualUmp; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -1391,7 +1393,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 +1530,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 { @@ -1551,6 +1551,12 @@ public class MidiService extends IMidiManager.Stub { return; } + if (!virtualUmp()) { + Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName + + ": virtual UMP flag not enabled"); + return; + } + Bundle properties = null; int numPorts = 0; boolean isPrivate = false; diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt index c1d137f3a8bd..93530cf5b0a9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -43,8 +43,7 @@ import kotlin.contracts.contract @Keep class AccessCheckingService(context: Context) : SystemService(context) { - @Volatile - private lateinit var state: AccessState + @Volatile private lateinit var state: AccessState private val stateLock = Any() private val policy = AccessPolicy() @@ -86,17 +85,22 @@ class AccessCheckingService(context: Context) : SystemService(context) { val state = MutableAccessState() policy.initialize( - state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback, - configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist, + state, + userIds, + packageStates, + disabledSystemPackageStates, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, implicitToSourcePermissions ) persistence.initialize() persistence.read(state) this.state = state - mutateState { - with(policy) { onInitialized() } - } + mutateState { with(policy) { onInitialized() } } appOpService.initialize() permissionService.initialize() @@ -106,40 +110,40 @@ class AccessCheckingService(context: Context) : SystemService(context) { get() = PackageManager.FEATURE_LEANBACK in availableFeatures private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String> - get() = MutableIndexedListSet<String>().apply { - this += "android" - if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { - // Note that SystemProperties.get(String, String) forces returning an empty string - // even if we pass null for the def parameter. - val carServicePackage = SystemProperties.get("ro.android.car.carservice.package") - if (carServicePackage.isNotEmpty()) { - this += carServicePackage + get() = + MutableIndexedListSet<String>().apply { + this += "android" + if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { + // Note that SystemProperties.get(String, String) forces returning an empty + // string + // even if we pass null for the def parameter. + val carServicePackage = + SystemProperties.get("ro.android.car.carservice.package") + if (carServicePackage.isNotEmpty()) { + this += carServicePackage + } } } - } private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> @Suppress("UNCHECKED_CAST") - get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { - splitPermissions.forEach { splitPermissionInfo -> - val sourcePermissionName = splitPermissionInfo.splitPermission - splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> - getOrPut(implicitPermissionName) { MutableIndexedListSet() } += - sourcePermissionName + get() = + MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { + splitPermissions.forEach { splitPermissionInfo -> + val sourcePermissionName = splitPermissionInfo.splitPermission + splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> + getOrPut(implicitPermissionName) { MutableIndexedListSet() } += + sourcePermissionName + } } - } - } as IndexedMap<String, IndexedListSet<String>> + } as IndexedMap<String, IndexedListSet<String>> internal fun onUserAdded(userId: Int) { - mutateState { - with(policy) { onUserAdded(userId) } - } + mutateState { with(policy) { onUserAdded(userId) } } } internal fun onUserRemoved(userId: Int) { - mutateState { - with(policy) { onUserRemoved(userId) } - } + mutateState { with(policy) { onUserRemoved(userId) } } } internal fun onStorageVolumeMounted( @@ -152,8 +156,12 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onStorageVolumeMounted( - packageStates, disabledSystemPackageStates, knownPackages, volumeUuid, - packageNames, isSystemUpdated + packageStates, + disabledSystemPackageStates, + knownPackages, + volumeUuid, + packageNames, + isSystemUpdated ) } } @@ -165,7 +173,10 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageAdded( - packageStates, disabledSystemPackageStates, knownPackages, packageName + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName ) } } @@ -177,7 +188,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageRemoved( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId ) } } @@ -189,7 +204,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageInstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, userId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + userId ) } } @@ -201,7 +220,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageUninstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId, + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId, userId ) } @@ -224,34 +247,42 @@ class AccessCheckingService(context: Context) : SystemService(context) { private fun PackageManagerInternal.getKnownPackages( packageStates: Map<String, PackageState> - ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply { - this[KnownPackages.PACKAGE_INSTALLER] = - getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_VERIFIER] = - getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SETUP_WIZARD] = - getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames( - KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_CONFIGURATOR] = - getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames( - KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_APP_PREDICTOR] = - getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_COMPANION] = - getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_RETAIL_DEMO] = - getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) - .filter { isProfileOwner(it, packageStates) }.toTypedArray() - this[KnownPackages.PACKAGE_RECENTS] = - getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) - } + ): IntMap<Array<String>> = + MutableIntMap<Array<String>>().apply { + this[KnownPackages.PACKAGE_INSTALLER] = + getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = + getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_VERIFIER] = + getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SETUP_WIZARD] = + getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = + getKnownPackageNames( + KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_CONFIGURATOR] = + getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = + getKnownPackageNames( + KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_APP_PREDICTOR] = + getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_COMPANION] = + getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_RETAIL_DEMO] = + getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) + .filter { isProfileOwner(it, packageStates) } + .toTypedArray() + this[KnownPackages.PACKAGE_RECENTS] = + getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) + } private fun isProfileOwner( packageName: String, diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt index a3f65af6bc82..d0913d29f504 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -38,16 +38,11 @@ import com.android.server.permission.access.util.writeWithReserveCopy import java.io.File import java.io.FileNotFoundException -class AccessPersistence( - private val policy: AccessPolicy -) { +class AccessPersistence(private val policy: AccessPolicy) { private val scheduleLock = Any() - @GuardedBy("scheduleLock") - private val pendingMutationTimesMillis = SparseLongArray() - @GuardedBy("scheduleLock") - private val pendingStates = MutableIntMap<AccessState>() - @GuardedBy("scheduleLock") - private lateinit var writeHandler: WriteHandler + @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray() + @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>() + @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler private val writeLock = Any() @@ -60,17 +55,16 @@ class AccessPersistence( */ fun read(state: MutableAccessState) { readSystemState(state) - state.externalState.userIds.forEachIndexed { _, userId -> - readUserState(state, userId) - } + state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) } } private fun readSystemState(state: MutableAccessState) { - val fileExists = systemFile.parse { - // This is the canonical way to call an extension function in a different class. - // TODO(b/259469752): Use context receiver for this when it becomes stable. - with(policy) { parseSystemState(state) } - } + val fileExists = + systemFile.parse { + // This is the canonical way to call an extension function in a different class. + // TODO(b/259469752): Use context receiver for this when it becomes stable. + with(policy) { parseSystemState(state) } + } if (!fileExists) { policy.migrateSystemState(state) @@ -79,9 +73,8 @@ class AccessPersistence( } private fun readUserState(state: MutableAccessState, userId: Int) { - val fileExists = getUserFile(userId).parse { - with(policy) { parseUserState(state, userId) } - } + val fileExists = + getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } } if (!fileExists) { policy.migrateUserState(state, userId) @@ -90,8 +83,8 @@ class AccessPersistence( } /** - * @return {@code true} if the file is successfully read from the disk; {@code false} if - * the file doesn't exist yet. + * @return {@code true} if the file is successfully read from the disk; {@code false} if the + * file doesn't exist yet. */ private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean = try { @@ -106,9 +99,7 @@ class AccessPersistence( fun write(state: AccessState) { state.systemState.write(state, UserHandle.USER_ALL) - state.userStates.forEachIndexed { _, userId, userState -> - userState.write(state, userId) - } + state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) } } private fun WritableState.write(state: AccessState, userId: Int) { @@ -127,8 +118,10 @@ class AccessPersistence( if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) { message.sendToTarget() } else { - val newDelayMillis = WRITE_DELAY_TIME_MILLIS - .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis) + val newDelayMillis = + WRITE_DELAY_TIME_MILLIS.coerceAtMost( + MAX_WRITE_DELAY_MILLIS - currentDelayMillis + ) writeHandler.sendMessageDelayed(message, newDelayMillis) } } @@ -161,15 +154,11 @@ class AccessPersistence( } private fun writeSystemState(state: AccessState) { - systemFile.serialize { - with(policy) { serializeSystemState(state) } - } + systemFile.serialize { with(policy) { serializeSystemState(state) } } } private fun writeUserState(state: AccessState, userId: Int) { - getUserFile(userId).serialize { - with(policy) { serializeUserState(state, userId) } - } + getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } } } private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 6a349e237ffe..754f77ec38f9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -37,21 +37,24 @@ import com.android.server.permission.access.util.tagName import com.android.server.pm.permission.PermissionAllowlist import com.android.server.pm.pkg.PackageState -class AccessPolicy private constructor( +class AccessPolicy +private constructor( private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>> ) { @Suppress("UNCHECKED_CAST") - constructor() : this( - MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { - fun addPolicy(policy: SchemePolicy) { - getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy - } - addPolicy(AppIdPermissionPolicy()) - addPolicy(DevicePermissionPolicy()) - addPolicy(AppIdAppOpPolicy()) - addPolicy(PackageAppOpPolicy()) - } as IndexedMap<String, IndexedMap<String, SchemePolicy>> - ) + constructor() : + this( + MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { + fun addPolicy(policy: SchemePolicy) { + getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = + policy + } + addPolicy(AppIdPermissionPolicy()) + addPolicy(DevicePermissionPolicy()) + addPolicy(AppIdAppOpPolicy()) + addPolicy(PackageAppOpPolicy()) + } as IndexedMap<String, IndexedMap<String, SchemePolicy>> + ) fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy = checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) { @@ -92,23 +95,17 @@ class AccessPolicy private constructor( } fun GetStateScope.onStateMutated() { - forEachSchemePolicy { - with(it) { onStateMutated() } - } + forEachSchemePolicy { with(it) { onStateMutated() } } } fun MutateStateScope.onInitialized() { - forEachSchemePolicy { - with(it) { onInitialized() } - } + forEachSchemePolicy { with(it) { onInitialized() } } } fun MutateStateScope.onUserAdded(userId: Int) { newState.mutateExternalState().mutateUserIds() += userId newState.mutateUserStatesNoWrite()[userId] = MutableUserState() - forEachSchemePolicy { - with(it) { onUserAdded(userId) } - } + forEachSchemePolicy { with(it) { onUserAdded(userId) } } newState.externalState.packageStates.forEach { (_, packageState) -> upgradePackageVersion(packageState, userId) } @@ -117,9 +114,7 @@ class AccessPolicy private constructor( fun MutateStateScope.onUserRemoved(userId: Int) { newState.mutateExternalState().mutateUserIds() -= userId newState.mutateUserStatesNoWrite() -= userId - forEachSchemePolicy { - with(it) { onUserRemoved(userId) } - } + forEachSchemePolicy { with(it) { onUserRemoved(userId) } } } fun MutateStateScope.onStorageVolumeMounted( @@ -154,9 +149,7 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } addedAppIds.forEachIndexed { _, appId -> - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } forEachSchemePolicy { with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) } @@ -192,13 +185,9 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } if (isAppIdAdded) { - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } - } - forEachSchemePolicy { - with(it) { onPackageAdded(packageState) } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } + forEachSchemePolicy { with(it) { onPackageAdded(packageState) } } newState.userStates.forEachIndexed { _, userId, _ -> upgradePackageVersion(packageState, userId) } @@ -227,13 +216,9 @@ class AccessPolicy private constructor( } setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageRemoved(packageName, appId) } - } + forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } } if (isAppIdRemoved) { - forEachSchemePolicy { - with(it) { onAppIdRemoved(appId) } - } + forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } } } newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (packageName in userState.packageVersions) { @@ -258,9 +243,7 @@ class AccessPolicy private constructor( checkNotNull(packageState) { "Installed package $packageName isn't found in packageStates in onPackageInstalled()" } - forEachSchemePolicy { - with(it) { onPackageInstalled(packageState, userId) } - } + forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } } } fun MutateStateScope.onPackageUninstalled( @@ -276,9 +259,7 @@ class AccessPolicy private constructor( setDisabledSystemPackageStates(disabledSystemPackageStates) setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageUninstalled(packageName, appId, userId) } - } + forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } } } fun MutateStateScope.onSystemReady( @@ -292,21 +273,15 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) setSystemReady(true) } - forEachSchemePolicy { - with(it) { onSystemReady() } - } + forEachSchemePolicy { with(it) { onSystemReady() } } } fun migrateSystemState(state: MutableAccessState) { - forEachSchemePolicy { - with(it) { migrateSystemState(state) } - } + forEachSchemePolicy { with(it) { migrateSystemState(state) } } } fun migrateUserState(state: MutableAccessState, userId: Int) { - forEachSchemePolicy { - with(it) { migrateUserState(state, userId) } - } + forEachSchemePolicy { with(it) { migrateUserState(state, userId) } } } private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) { @@ -330,10 +305,12 @@ class AccessPolicy private constructor( VERSION_LATEST } version == VERSION_LATEST -> {} - else -> Slog.w( - LOG_TAG, "Unexpected version $version for package $packageName," + - "latest version is $VERSION_LATEST" - ) + else -> + Slog.w( + LOG_TAG, + "Unexpected version $version for package $packageName," + + "latest version is $VERSION_LATEST" + ) } } @@ -341,11 +318,7 @@ class AccessPolicy private constructor( forEachTag { when (tagName) { TAG_ACCESS -> { - forEachTag { - forEachSchemePolicy { - with(it) { parseSystemState(state) } - } - } + forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } } } else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") } @@ -353,11 +326,7 @@ class AccessPolicy private constructor( } fun BinaryXmlSerializer.serializeSystemState(state: AccessState) { - tag(TAG_ACCESS) { - forEachSchemePolicy { - with(it) { serializeSystemState(state) } - } - } + tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } } } fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { @@ -370,9 +339,7 @@ class AccessPolicy private constructor( TAG_DEFAULT_PERMISSION_GRANT -> parseDefaultPermissionGrant(state, userId) else -> { - forEachSchemePolicy { - with(it) { parseUserState(state, userId) } - } + forEachSchemePolicy { with(it) { parseUserState(state, userId) } } } } } @@ -428,9 +395,7 @@ class AccessPolicy private constructor( serializeDefaultPermissionGrantFingerprint( state.userStates[userId]!!.defaultPermissionGrantFingerprint ) - forEachSchemePolicy { - with(it) { serializeUserState(state, userId) } - } + forEachSchemePolicy { with(it) { serializeUserState(state, userId) } } } } @@ -451,9 +416,7 @@ class AccessPolicy private constructor( fingerprint: String? ) { if (fingerprint != null) { - tag(TAG_DEFAULT_PERMISSION_GRANT) { - attributeInterned(ATTR_FINGERPRINT, fingerprint) - } + tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) } } } @@ -462,9 +425,7 @@ class AccessPolicy private constructor( private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) { schemePolicies.forEachIndexed { _, _, objectSchemePolicies -> - objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> - action(schemePolicy) - } + objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) } } } diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 94c878a453c9..49d2f813c487 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -28,7 +28,9 @@ private typealias ExternalStateReference = MutableReference<ExternalState, Mutab private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState> typealias UserStates = IntReferenceMap<UserState, MutableUserState> + typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState> + private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates> sealed class AccessState( @@ -48,22 +50,22 @@ sealed class AccessState( override fun toMutable(): MutableAccessState = MutableAccessState(this) } -class MutableAccessState private constructor( +class MutableAccessState +private constructor( externalStateReference: ExternalStateReference, systemStateReference: SystemStateReference, userStatesReference: UserStatesReference -) : AccessState( - externalStateReference, - systemStateReference, - userStatesReference -) { - constructor() : this( - ExternalStateReference(MutableExternalState()), - SystemStateReference(MutableSystemState()), - UserStatesReference(MutableUserStates()) - ) - - internal constructor(accessState: AccessState) : this( +) : AccessState(externalStateReference, systemStateReference, userStatesReference) { + constructor() : + this( + ExternalStateReference(MutableExternalState()), + SystemStateReference(MutableSystemState()), + UserStatesReference(MutableUserStates()) + ) + + internal constructor( + accessState: AccessState + ) : this( accessState.externalStateReference.toImmutable(), accessState.systemStateReference.toImmutable(), accessState.userStatesReference.toImmutable() @@ -86,8 +88,10 @@ class MutableAccessState private constructor( private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet> typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + typealias MutableAppIdPackageNames = MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + private typealias AppIdPackageNamesReference = MutableReference<AppIdPackageNames, MutableAppIdPackageNames> @@ -142,7 +146,8 @@ sealed class ExternalState( override fun toMutable(): MutableExternalState = MutableExternalState(this) } -class MutableExternalState private constructor( +class MutableExternalState +private constructor( userIdsReference: UserIdsReference, packageStates: Map<String, PackageState>, disabledSystemPackageStates: Map<String, PackageState>, @@ -154,34 +159,38 @@ class MutableExternalState private constructor( permissionAllowlist: PermissionAllowlist, implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>, isSystemReady: Boolean -) : ExternalState( - userIdsReference, - packageStates, - disabledSystemPackageStates, - appIdPackageNamesReference, - knownPackages, - isLeanback, - configPermissions, - privilegedPermissionAllowlistPackages, - permissionAllowlist, - implicitToSourcePermissions, - isSystemReady -) { - constructor() : this( - UserIdsReference(MutableIntSet()), - emptyMap(), - emptyMap(), - AppIdPackageNamesReference(MutableAppIdPackageNames()), - MutableIntMap(), - false, - emptyMap(), - MutableIndexedListSet(), - PermissionAllowlist(), - MutableIndexedMap(), - false - ) - - internal constructor(externalState: ExternalState) : this( +) : + ExternalState( + userIdsReference, + packageStates, + disabledSystemPackageStates, + appIdPackageNamesReference, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, + implicitToSourcePermissions, + isSystemReady + ) { + constructor() : + this( + UserIdsReference(MutableIntSet()), + emptyMap(), + emptyMap(), + AppIdPackageNamesReference(MutableAppIdPackageNames()), + MutableIntMap(), + false, + emptyMap(), + MutableIndexedListSet(), + PermissionAllowlist(), + MutableIndexedMap(), + false + ) + + internal constructor( + externalState: ExternalState + ) : this( externalState.userIdsReference.toImmutable(), externalState.packageStates, externalState.disabledSystemPackageStates, @@ -249,9 +258,10 @@ class MutableExternalState private constructor( } } -private typealias PermissionGroupsReference = MutableReference< - IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> -> +private typealias PermissionGroupsReference = + MutableReference< + IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> + > private typealias PermissionTreesReference = MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>> @@ -280,25 +290,31 @@ sealed class SystemState( override fun toMutable(): MutableSystemState = MutableSystemState(this) } -class MutableSystemState private constructor( +class MutableSystemState +private constructor( permissionGroupsReference: PermissionGroupsReference, permissionTreesReference: PermissionTreesReference, permissionsReference: PermissionsReference, writeMode: Int -) : SystemState( - permissionGroupsReference, - permissionTreesReference, - permissionsReference, - writeMode -), MutableWritableState { - constructor() : this( - PermissionGroupsReference(MutableIndexedMap()), - PermissionTreesReference(MutableIndexedMap()), - PermissionsReference(MutableIndexedMap()), - WriteMode.NONE - ) - - internal constructor(systemState: SystemState) : this( +) : + SystemState( + permissionGroupsReference, + permissionTreesReference, + permissionsReference, + writeMode + ), + MutableWritableState { + constructor() : + this( + PermissionGroupsReference(MutableIndexedMap()), + PermissionTreesReference(MutableIndexedMap()), + PermissionsReference(MutableIndexedMap()), + WriteMode.NONE + ) + + internal constructor( + systemState: SystemState + ) : this( systemState.permissionGroupsReference.toImmutable(), systemState.permissionTreesReference.toImmutable(), systemState.permissionsReference.toImmutable(), @@ -311,8 +327,7 @@ class MutableSystemState private constructor( fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> = permissionTreesReference.mutate() - fun mutatePermissions(): MutableIndexedMap<String, Permission> = - permissionsReference.mutate() + fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate() override fun requestWriteMode(writeMode: Int) { this.writeMode = maxOf(this.writeMode, writeMode) @@ -324,34 +339,42 @@ private typealias PackageVersionsReference = typealias AppIdPermissionFlags = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdPermissionFlags = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> - typealias DevicePermissionFlags = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableDevicePermissionFlags = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias AppIdDevicePermissionFlags = IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + typealias MutableAppIdDevicePermissionFlags = MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + private typealias AppIdDevicePermissionFlagsReference = MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> -typealias AppIdAppOpModes = - IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdAppOpModes = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdAppOpModesReference = MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes> typealias PackageAppOpModes = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutablePackageAppOpModes = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias PackageAppOpModesReference = MutableReference<PackageAppOpModes, MutablePackageAppOpModes> @@ -388,7 +411,8 @@ sealed class UserState( override fun toMutable(): MutableUserState = MutableUserState(this) } -class MutableUserState private constructor( +class MutableUserState +private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, @@ -396,26 +420,31 @@ class MutableUserState private constructor( packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, writeMode: Int -) : UserState( - packageVersionsReference, - appIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference, - appIdAppOpModesReference, - packageAppOpModesReference, - defaultPermissionGrantFingerprint, - writeMode -), MutableWritableState { - constructor() : this( - PackageVersionsReference(MutableIndexedMap<String, Int>()), - AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), - AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), - AppIdAppOpModesReference(MutableAppIdAppOpModes()), - PackageAppOpModesReference(MutablePackageAppOpModes()), - null, - WriteMode.NONE - ) - - internal constructor(userState: UserState) : this( +) : + UserState( + packageVersionsReference, + appIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference, + appIdAppOpModesReference, + packageAppOpModesReference, + defaultPermissionGrantFingerprint, + writeMode + ), + MutableWritableState { + constructor() : + this( + PackageVersionsReference(MutableIndexedMap<String, Int>()), + AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), + AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), + AppIdAppOpModesReference(MutableAppIdAppOpModes()), + PackageAppOpModesReference(MutablePackageAppOpModes()), + null, + WriteMode.NONE + ) + + internal constructor( + userState: UserState + ) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), userState.appIdDevicePermissionFlagsReference.toImmutable(), @@ -461,11 +490,7 @@ interface MutableWritableState : WritableState { fun requestWriteMode(writeMode: Int) } -open class GetStateScope( - val state: AccessState -) +open class GetStateScope(val state: AccessState) -class MutateStateScope( - val oldState: AccessState, - val newState: MutableAccessState -) : GetStateScope(newState) +class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) : + GetStateScope(newState) diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index 1d46ca71fe0f..1f5a4df33cc9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -18,9 +18,7 @@ package com.android.server.permission.access import android.os.UserHandle -sealed class AccessUri( - val scheme: String -) { +sealed class AccessUri(val scheme: String) { override fun equals(other: Any?): Boolean { throw NotImplementedError() } @@ -34,9 +32,7 @@ sealed class AccessUri( } } -data class AppOpUri( - val appOpName: String -) : AccessUri(SCHEME) { +data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$appOpName" companion object { @@ -44,10 +40,7 @@ data class AppOpUri( } } -data class PackageUri( - val packageName: String, - val userId: Int -) : AccessUri(SCHEME) { +data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$packageName/$userId" companion object { @@ -55,9 +48,7 @@ data class PackageUri( } } -data class PermissionUri( - val permissionName: String -) : AccessUri(SCHEME) { +data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName" companion object { @@ -65,10 +56,7 @@ data class PermissionUri( } } -data class DevicePermissionUri( - val permissionName: String, - val deviceId: Int -) : AccessUri(SCHEME) { +data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName/$deviceId" companion object { @@ -76,9 +64,7 @@ data class DevicePermissionUri( } } -data class UidUri( - val uid: Int -) : AccessUri(SCHEME) { +data class UidUri(val uid: Int) : AccessUri(SCHEME) { val userId: Int get() = UserHandle.getUserId(uid) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt index 96d315e923ba..6c1b080e4ff4 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt @@ -46,9 +46,7 @@ class AppIdAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() appIdAppOpModes[appId] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } if (packageNames != null) { val packageVersions = userState.mutatePackageVersions() diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt index 4c7e94688d00..f291b1ab77e9 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt @@ -51,8 +51,10 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() { } userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ -> // Non-application UIDs may not have an Android package but may still have app op state. - if (appId !in state.externalState.appIdPackageNames && - appId >= Process.FIRST_APPLICATION_UID) { + if ( + appId !in state.externalState.appIdPackageNames && + appId >= Process.FIRST_APPLICATION_UID + ) { Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") appIdAppOpModes.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index c02fe4df67c0..94caf2865b66 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -46,7 +46,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId) if (appIdIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutateAppIdAppOpModes() .removeAt(appIdIndex) // Skip notifying the change listeners since the app ID no longer exists. } @@ -61,8 +63,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { if (userStateIndex < 0) { return false } - val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes - .indexOfKey(appId) + val appIdIndex = + newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId) if (appIdIndex < 0) { return false } @@ -71,7 +73,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { } fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int = - state.userStates[userId]?.appIdAppOpModes?.get(appId) + state.userStates[userId] + ?.appIdAppOpModes + ?.get(appId) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -81,8 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .appIdAppOpModes[appId] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -122,9 +128,7 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt index 12df95e26ec4..10c77645bf15 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt @@ -28,11 +28,13 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND) setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode + packageState.appId, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + appOpMode ) } } @@ -40,14 +42,19 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (permissionName in packageState.androidPackage!!.requestedPermissions) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM - ) + val appOpMode = + getAppOpMode( + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + ) val defaultAppOpMode = AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (appOpMode == defaultAppOpMode) { setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED ) } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 5b91ad9834df..26ea9d24f918 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -33,19 +33,16 @@ import com.android.server.permission.access.UidUri import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set -class AppOpService( - private val service: AccessCheckingService -) : AppOpsCheckingServiceInterface { - private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) - as PackageAppOpPolicy - private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) - as AppIdAppOpPolicy +class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { + private val packagePolicy = + service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy + private val appIdPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy private val context = service.context private lateinit var handler: Handler - @Volatile - private var listeners = ArraySet<AppOpsModeChangedListener>() + @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() private val listenersLock = Any() fun initialize() { @@ -86,9 +83,7 @@ class AppOpService( val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { - with(appIdPolicy) { getAppOpMode(appId, userId, opName) } - } + return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -115,10 +110,7 @@ class AppOpService( } } - private fun getPackageModes( - packageName: String, - userId: Int - ): ArrayMap<String, Int>? = + private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { @@ -131,15 +123,13 @@ class AppOpService( override fun removeUid(uid: Int) { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - service.mutateState { - with(appIdPolicy) { removeAppOpModes(appId, userId) } - } + service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } } } override fun removePackage(packageName: String, userId: Int): Boolean { var wasChanged = false service.mutateState { - wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) } + wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } return wasChanged } diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt index a267637dd0c4..edeef713e6d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -52,9 +52,7 @@ abstract class BaseAppOpPersistence { } protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) { - appOpModes.forEachIndexed { _, name, mode -> - serializeAppOp(name, mode) - } + appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) } } private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) { diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt index c0a85f8e4b3f..758cec00da50 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.AppOpUri import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.SchemePolicy -abstract class BaseAppOpPolicy( - private val persistence: BaseAppOpPersistence -) : SchemePolicy() { +abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() { override val objectScheme: String get() = AppOpUri.SCHEME diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt index 03311a238410..8797e39754d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt @@ -44,9 +44,7 @@ class PackageAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() packageAppOpModes[packageName] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } userState.mutatePackageVersions()[packageName] = version } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 5398a57d084e..0d9470edc4ea 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -46,7 +46,9 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName) if (packageNameIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) // Skip notifying the change listeners since the package no longer exists. } @@ -61,18 +63,22 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { if (userStateIndex < 0) { return false } - val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes - .indexOfKey(packageName) + val packageNameIndex = + newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName) if (packageNameIndex < 0) { return false } - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) return true } fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int = - state.userStates[userId]?.packageAppOpModes?.get(packageName) + state.userStates[userId] + ?.packageAppOpModes + ?.get(packageName) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -82,8 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .packageAppOpModes[packageName] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -123,9 +131,7 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt index 8e370936291f..f5eedf714158 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt @@ -28,11 +28,16 @@ class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode( + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_IN_BACKGROUND + ) setAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode ) } diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt index 686db42bbe63..b74f47734701 100644 --- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt @@ -49,7 +49,9 @@ inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> U } inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index ce4aa4446698..ea8e07fd31ff 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,12 +16,8 @@ package com.android.server.permission.access.immutable -/** - * Immutable list with index-based access. - */ -sealed class IndexedList<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedList<T>> { +/** Immutable list with index-based access. */ +sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> { val size: Int get() = list.size @@ -29,20 +25,15 @@ sealed class IndexedList<T>( operator fun contains(element: T): Boolean = list.contains(element) - @Suppress("ReplaceGetOrSet") - operator fun get(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this) override fun toString(): String = list.toString() } -/** - * Mutable list with index-based access. - */ -class MutableIndexedList<T>( - list: ArrayList<T> = ArrayList() -) : IndexedList<T>(list) { +/** Mutable list with index-based access. */ +class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) { constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list)) @Suppress("ReplaceGetOrSet") diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt index dc9bae323662..a9d804ec2fd2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedList<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 77e71baf0ab7..3a2fd2f386c8 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,12 +16,9 @@ package com.android.server.permission.access.immutable -/** - * Immutable set with index-based access, implemented using a list. - */ -sealed class IndexedListSet<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedListSet<T>> { +/** Immutable set with index-based access, implemented using a list. */ +sealed class IndexedListSet<T>(internal val list: ArrayList<T>) : + Immutable<MutableIndexedListSet<T>> { val size: Int get() = list.size @@ -31,20 +28,15 @@ sealed class IndexedListSet<T>( fun indexOf(element: T): Int = list.indexOf(element) - @Suppress("ReplaceGetOrSet") - fun elementAt(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this) override fun toString(): String = list.toString() } -/** - * Mutable set with index-based access, implemented using a list. - */ -class MutableIndexedListSet<T>( - list: ArrayList<T> = ArrayList() -) : IndexedListSet<T>(list) { +/** Mutable set with index-based access, implemented using a list. */ +class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) { constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list)) fun add(element: T): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt index 13fc141c5a66..2634b5308049 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedListSet<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 299cc89d9a07..873c9c83607e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,12 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap -/** - * Immutable map with index-based access. - */ -sealed class IndexedMap<K, V>( - internal val map: ArrayMap<K, V> -) : Immutable<MutableIndexedMap<K, V>> { +/** Immutable map with index-based access. */ +sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) : + Immutable<MutableIndexedMap<K, V>> { val size: Int get() = map.size @@ -31,8 +28,7 @@ sealed class IndexedMap<K, V>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): V? = map.get(key) + @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key) fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -45,12 +41,8 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } -/** - * Mutable map with index-based access. - */ -class MutableIndexedMap<K, V>( - map: ArrayMap<K, V> = ArrayMap() -) : IndexedMap<K, V>(map) { +/** Mutable map with index-based access. */ +class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) { constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map)) fun put(key: K, value: V): V? = map.put(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt index 69f1779cff8d..48637cc271bf 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean) inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -75,9 +77,7 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo( destination: C, transform: (Int, K, V) -> R, ): C { - forEachIndexed { index, key, value -> - transform(index, key, value).let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } } return destination } @@ -85,14 +85,14 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexe destination: C, transform: (Int, K, V) -> R? ): C { - forEachIndexed { index, key, value -> - transform(index, key, value)?.let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } } return destination } inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index ff76a4745c8b..6fe471802878 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): I? = map.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get() fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { - constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this( + constructor( + indexedReferenceMap: IndexedReferenceMap<K, I, M> + ) : this( ArrayMap(indexedReferenceMap.map).apply { for (i in 0 until size) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: K): M? = map.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate() fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt index 22b4d521176e..43a902b23ba5 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.muta key: K, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 547e56cef62a..cbc24b1a64a7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.ArraySet -/** - * Immutable set with index-based access. - */ -sealed class IndexedSet<T>( - internal val set: ArraySet<T> -) : Immutable<MutableIndexedSet<T>> { +/** Immutable set with index-based access. */ +sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> { val size: Int get() = set.size @@ -40,12 +36,8 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } -/** - * Mutable set with index-based access. - */ -class MutableIndexedSet<T>( - set: ArraySet<T> = ArraySet() -) : IndexedSet<T>(set) { +/** Mutable set with index-based access. */ +class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) { constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set)) fun add(element: T): Boolean = set.add(element) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index 7ed29e8813ac..e9a405f9fd6f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseArray -/** - * Immutable map with index-based access and [Int] keys. - */ -sealed class IntMap<T>( - internal val array: SparseArray<T> -) : Immutable<MutableIntMap<T>> { +/** Immutable map with index-based access and [Int] keys. */ +sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> { val size: Int get() = array.size() @@ -44,12 +40,8 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } -/** - * Mutable map with index-based access and [Int] keys. - */ -class MutableIntMap<T>( - array: SparseArray<T> = SparseArray() -) : IntMap<T>(array) { +/** Mutable map with index-based access and [Int] keys. */ +class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) { constructor(intMap: IntMap<T>) : this(intMap.array.clone()) fun put(key: Int, value: T): T? = array.putReturnOld(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt index 9aa0a4182eb7..09d7319ec62e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolea inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -72,7 +74,9 @@ inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boole } inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 160b2279a0ba..3f2651736a54 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( operator fun contains(key: Int): Boolean = array.contains(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: Int): I? = array.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get() fun indexOfKey(key: Int): Int = array.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { - constructor(intReferenceMap: IntReferenceMap<I, M>) : this( + constructor( + intReferenceMap: IntReferenceMap<I, M> + ) : this( intReferenceMap.array.clone().apply { for (i in 0 until size()) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIntReferenceMap<I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: Int): M? = array.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate() fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt index 1ed4f8a777d2..a1bab9579c3d 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut( key: Int, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 21f2af20c3a9..125479706586 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray -/** - * Immutable set with index-based access and [Int] elements. - */ -sealed class IntSet( - internal val array: SparseBooleanArray -) : Immutable<MutableIntSet> { +/** Immutable set with index-based access and [Int] elements. */ +sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> { val size: Int get() = array.size() @@ -40,12 +36,8 @@ sealed class IntSet( override fun toString(): String = array.toString() } -/** - * Mutable set with index-based access and [Int] elements. - */ -class MutableIntSet( - array: SparseBooleanArray = SparseBooleanArray() -) : IntSet(array) { +/** Mutable set with index-based access and [Int] elements. */ +class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) { constructor(intSet: IntSet) : this(intSet.array.clone()) fun add(element: Int): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt index 163ebbf85aed..9d0d14f01ce0 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt @@ -66,7 +66,7 @@ inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean { operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element } -fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values } +fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values } operator fun MutableIntSet.plusAssign(element: Int) { array.put(element, true) diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index 171cfeb4379d..471a71b04635 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -27,21 +27,17 @@ package com.android.server.permission.access.immutable * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the * data structure is mutated/copied, a new instance of this class should be obtained with - * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents - * further modifications to a data structure accessed with its immutable interface. + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further + * modifications to a data structure accessed with its immutable interface. * * @see MutableIndexedReferenceMap * @see MutableIntReferenceMap */ -class MutableReference<I : Immutable<M>, M : I> private constructor( - private var immutable: I, - private var mutable: M? -) { +class MutableReference<I : Immutable<M>, M : I> +private constructor(private var immutable: I, private var mutable: M?) { constructor(mutable: M) : this(mutable, mutable) - /** - * Return an immutable reference to the wrapped mutable data structure. - */ + /** Return an immutable reference to the wrapped mutable data structure. */ fun get(): I = immutable /** @@ -50,7 +46,9 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( * already mutable. */ fun mutate(): M { - mutable?.let { return it } + mutable?.let { + return it + } return immutable.toMutable().also { immutable = it mutable = it diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt index 691ed8f10220..29838959ccb3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.immutable.* // ktlint-disable no-wil import com.android.server.permission.access.util.PackageVersionMigration import com.android.server.pm.permission.PermissionMigrationHelper -/** - * This class migrate legacy permissions to unified permission subsystem - */ +/** This class migrate legacy permissions to unified permission subsystem */ class AppIdPermissionMigration { internal fun migrateSystemState(state: MutableAccessState) { val legacyPermissionsManager = @@ -34,10 +32,15 @@ class AppIdPermissionMigration { return } - migratePermissions(state.mutateSystemState().mutatePermissions(), - legacyPermissionsManager.legacyPermissions) - migratePermissions(state.mutateSystemState().mutatePermissionTrees(), - legacyPermissionsManager.legacyPermissionTrees, true) + migratePermissions( + state.mutateSystemState().mutatePermissions(), + legacyPermissionsManager.legacyPermissions + ) + migratePermissions( + state.mutateSystemState().mutatePermissionTrees(), + legacyPermissionsManager.legacyPermissionTrees, + true + ) } private fun migratePermissions( @@ -46,14 +49,15 @@ class AppIdPermissionMigration { isPermissionTree: Boolean = false ) { legacyPermissions.forEach { (_, legacyPermission) -> - val permission = Permission( - legacyPermission.permissionInfo, false, legacyPermission.type, 0 - ) + val permission = + Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0) permissions[permission.name] = permission if (DEBUG_MIGRATION) { - Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + - "${permission.type}, appId: ${permission.appId}, protectionLevel: " + - "${permission.protectionLevel}, tree: $isPermissionTree" + Slog.v( + LOG_TAG, + "Migrated permission: ${permission.name}, type: " + + "${permission.type}, appId: ${permission.appId}, protectionLevel: " + + "${permission.protectionLevel}, tree: $isPermissionTree" ) } } @@ -81,25 +85,23 @@ class AppIdPermissionMigration { val permissionFlags = MutableIndexedMap<String, Int>() appIdPermissionFlags[appId] = permissionFlags - legacyPermissionStates.forEach forEachPermission@ { + legacyPermissionStates.forEach forEachPermission@{ (permissionName, legacyPermissionState) -> val permission = state.systemState.permissions[permissionName] if (permission == null) { Slog.w( - LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" + + LOG_TAG, + "Dropping unknown permission $permissionName for app ID $appId" + " when migrating permission state" ) return@forEachPermission } - permissionFlags[permissionName] = migratePermissionFlags( - permission, legacyPermissionState, appId, userId - ) + permissionFlags[permissionName] = + migratePermissionFlags(permission, legacyPermissionState, appId, userId) } val packageVersions = userState.mutatePackageVersions() - packageNames.forEachIndexed { _, packageName -> - packageVersions[packageName] = version - } + packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version } } } @@ -109,29 +111,35 @@ class AppIdPermissionMigration { appId: Int, userId: Int ): Int { - var flags = when { - permission.isNormal -> if (legacyPermissionState.isGranted) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } - permission.isSignature || permission.isInternal -> - if (legacyPermissionState.isGranted) { - if (permission.isDevelopment || permission.isRole) { - PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + var flags = + when { + permission.isNormal -> + if (legacyPermissionState.isGranted) { + PermissionFlags.INSTALL_GRANTED } else { - PermissionFlags.PROTECTION_GRANTED + PermissionFlags.INSTALL_REVOKED } - } else { - 0 - } - permission.isRuntime -> - if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 - else -> 0 - } - flags = PermissionFlags.updateFlags( - permission, flags, legacyPermissionState.flags, legacyPermissionState.flags - ) + permission.isSignature || permission.isInternal -> + if (legacyPermissionState.isGranted) { + if (permission.isDevelopment || permission.isRole) { + PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + } else { + PermissionFlags.PROTECTION_GRANTED + } + } else { + 0 + } + permission.isRuntime -> + if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 + else -> 0 + } + flags = + PermissionFlags.updateFlags( + permission, + flags, + legacyPermissionState.flags, + legacyPermissionState.flags + ) if (DEBUG_MIGRATION) { val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags) val newFlagString = PermissionFlags.toString(flags) @@ -139,7 +147,8 @@ class AppIdPermissionMigration { val newGrantState = PermissionFlags.isPermissionGranted(flags) val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags) Slog.v( - LOG_TAG, "Migrated appId: $appId, permission: " + + LOG_TAG, + "Migrated appId: $appId, permission: " + "${permission.name}, user: $userId, oldGrantState: $oldGrantState" + ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " + "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch" diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt index 2c8175b585af..1f40f01ffef4 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt @@ -57,11 +57,12 @@ class AppIdPermissionPersistence { isPermissionTree: Boolean ) { val systemState = state.mutateSystemState(WriteMode.NONE) - val permissions = if (isPermissionTree) { - systemState.mutatePermissionTrees() - } else { - systemState.mutatePermissions() - } + val permissions = + if (isPermissionTree) { + systemState.mutatePermissionTrees() + } else { + systemState.mutatePermissions() + } forEachTag { when (val tagName = tagName) { TAG_PERMISSION -> parsePermission(permissions) @@ -71,10 +72,13 @@ class AppIdPermissionPersistence { permissions.forEachReversedIndexed { permissionIndex, _, permission -> val packageName = permission.packageName val externalState = state.externalState - if (packageName !in externalState.packageStates && - packageName !in externalState.disabledSystemPackageStates) { + if ( + packageName !in externalState.packageStates && + packageName !in externalState.disabledSystemPackageStates + ) { Slog.w( - LOG_TAG, "Dropping permission ${permission.name} from unknown package" + + LOG_TAG, + "Dropping permission ${permission.name} from unknown package" + " $packageName when parsing permissions" ) permissions.removeAt(permissionIndex) @@ -88,11 +92,12 @@ class AppIdPermissionPersistence { ) { val name = getAttributeValueOrThrow(ATTR_NAME).intern() @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - this.name = name - packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() - protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) - } + val permissionInfo = + PermissionInfo().apply { + this.name = name + packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() + protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) + } val type = getAttributeIntOrThrow(ATTR_TYPE) when (type) { Permission.TYPE_MANIFEST -> {} @@ -125,15 +130,14 @@ class AppIdPermissionPersistence { tagName: String, permissions: IndexedMap<String, Permission> ) { - tag(tagName) { - permissions.forEachIndexed { _, _, it -> serializePermission(it) } - } + tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } } } private fun BinaryXmlSerializer.serializePermission(permission: Permission) { val type = permission.type when (type) { - Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} + Permission.TYPE_MANIFEST, + Permission.TYPE_DYNAMIC -> {} Permission.TYPE_CONFIG -> return else -> { Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") @@ -228,11 +232,12 @@ class AppIdPermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 345f101cbc14..08ba75397a09 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -55,7 +55,8 @@ class AppIdPermissionPolicy : SchemePolicy() { @Volatile private var onPermissionFlagsChangedListeners: - IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet() + IndexedListSet<OnPermissionFlagsChangedListener> = + MutableIndexedListSet() private val onPermissionFlagsChangedListenersLock = Any() private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() @@ -73,30 +74,37 @@ class AppIdPermissionPolicy : SchemePolicy() { override fun MutateStateScope.onInitialized() { newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> val oldPermission = newState.systemState.permissions[permissionName] - val newPermission = if (oldPermission != null) { - if (permissionEntry.gids != null) { - oldPermission.copy( - gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser - ) - } else { - return@forEach - } - } else { - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - packageName = PLATFORM_PACKAGE_NAME - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE - } - if (permissionEntry.gids != null) { - Permission( - permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids, - permissionEntry.perUser - ) + val newPermission = + if (oldPermission != null) { + if (permissionEntry.gids != null) { + oldPermission.copy( + gids = permissionEntry.gids, + areGidsPerUser = permissionEntry.perUser + ) + } else { + return@forEach + } } else { - Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + @Suppress("DEPRECATION") + val permissionInfo = + PermissionInfo().apply { + name = permissionName + packageName = PLATFORM_PACKAGE_NAME + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + } + if (permissionEntry.gids != null) { + Permission( + permissionInfo, + false, + Permission.TYPE_CONFIG, + 0, + permissionEntry.gids, + permissionEntry.perUser + ) + } else { + Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + } } - } newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission } } @@ -200,30 +208,32 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isHardOrSoftRestricted) { return@forEach } - val isRequestedBySystemPackage = anyPackageInAppId(appId) { - it.isSystem && permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedBySystemPackage = + anyPackageInAppId(appId) { + it.isSystem && permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedBySystemPackage) { return@forEach } val oldFlags = getPermissionFlags(appId, userId, permissionName) var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -243,15 +253,15 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isRuntime || permission.isRemoved) { return@forEach } - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -260,13 +270,15 @@ class AppIdPermissionPolicy : SchemePolicy() { return@forEach } var newFlags = oldFlags - newFlags = if ( - newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT) - ) { - newFlags or PermissionFlags.RUNTIME_GRANTED - } else { - newFlags andInv PermissionFlags.RUNTIME_GRANTED - } + newFlags = + if ( + newFlags.hasBits(PermissionFlags.ROLE) || + newFlags.hasBits(PermissionFlags.PREGRANT) + ) { + newFlags or PermissionFlags.RUNTIME_GRANTED + } else { + newFlags andInv PermissionFlags.RUNTIME_GRANTED + } newFlags = newFlags andInv USER_SETTABLE_MASK if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) { newFlags = newFlags or PermissionFlags.IMPLICIT @@ -285,24 +297,32 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!canAdoptPermissions(packageName, originalPackageName)) { return@forEachIndexed } - newState.systemState.permissions.forEachIndexed permissions@ { - permissionIndex, permissionName, oldPermission -> + newState.systemState.permissions.forEachIndexed permissions@{ + permissionIndex, + permissionName, + oldPermission -> if (oldPermission.packageName != originalPackageName) { return@permissions } @Suppress("DEPRECATION") - val newPermissionInfo = PermissionInfo().apply { - name = oldPermission.permissionInfo.name - this.packageName = packageName - protectionLevel = oldPermission.permissionInfo.protectionLevel - } + val newPermissionInfo = + PermissionInfo().apply { + name = oldPermission.permissionInfo.name + this.packageName = packageName + protectionLevel = oldPermission.permissionInfo.protectionLevel + } // Different from the old implementation, which removes the GIDs upon permission // adoption, but adds them back on the next boot, we now just consistently keep the // GIDs. - val newPermission = oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = false, appId = 0 - ) - newState.mutateSystemState().mutatePermissions() + val newPermission = + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = false, + appId = 0 + ) + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, newPermission) changedPermissionNames += permissionName } @@ -313,18 +333,20 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName: String, originalPackageName: String ): Boolean { - val originalPackageState = newState.externalState.packageStates[originalPackageName] - ?: return false + val originalPackageState = + newState.externalState.packageStates[originalPackageName] ?: return false if (!originalPackageState.isSystem) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package not in system partition" ) return false } if (originalPackageState.androidPackage != null) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package still exists" ) return false @@ -339,20 +361,25 @@ class AppIdPermissionPolicy : SchemePolicy() { val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } if (isInstantApp) { Slog.w( - LOG_TAG, "Ignoring permission groups declared in package" + + LOG_TAG, + "Ignoring permission groups declared in package" + " ${packageState.packageName}: instant apps cannot declare permission groups" ) return } packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup -> - val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo( - parsedPermissionGroup, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionGroup = + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + )!! // TODO: Clear permission state on group take-over? val permissionGroupName = newPermissionGroup.name val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName] - if (oldPermissionGroup != null && - newPermissionGroup.packageName != oldPermissionGroup.packageName) { + if ( + oldPermissionGroup != null && + newPermissionGroup.packageName != oldPermissionGroup.packageName + ) { val newPackageName = newPermissionGroup.packageName val oldPackageName = oldPermissionGroup.packageName // Different from the old implementation, which defines permission group on @@ -361,7 +388,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // to permissions so that we no longer need to rely on the scan order. if (!packageState.isSystem) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " package $newPackageName: already declared in another" + " package $oldPackageName" ) @@ -369,14 +397,16 @@ class AppIdPermissionPolicy : SchemePolicy() { } if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " system package $newPackageName: already declared in another" + " system package $oldPackageName" ) return@forEachIndexed } Slog.w( - LOG_TAG, "Overriding permission group $permissionGroupName with" + + LOG_TAG, + "Overriding permission group $permissionGroupName with" + " new declaration in system package $newPackageName: originally" + " declared in another package $oldPackageName" ) @@ -393,20 +423,23 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage!! // This may not be the same package as the old permission because the old permission owner // can be different, hence using this somewhat strange name to prevent misuse. - val oldNewPackage = oldState.externalState.packageStates[packageState.packageName] - ?.androidPackage - val isPackageSigningChanged = oldNewPackage != null && - androidPackage.signingDetails != oldNewPackage.signingDetails + val oldNewPackage = + oldState.externalState.packageStates[packageState.packageName]?.androidPackage + val isPackageSigningChanged = + oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails androidPackage.permissions.forEachIndexed { _, parsedPermission -> - val newPermissionInfo = PackageInfoUtils.generatePermissionInfo( - parsedPermission, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionInfo = + PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! val permissionName = newPermissionInfo.name - val oldPermission = if (parsedPermission.isTree) { - newState.systemState.permissionTrees[permissionName] - } else { - newState.systemState.permissions[permissionName] - } + val oldPermission = + if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] + } else { + newState.systemState.permissions[permissionName] + } // Different from the old implementation, which may add an (incomplete) signature // permission inside another package's permission tree, we now consistently ignore such // permissions. @@ -414,128 +447,152 @@ class AppIdPermissionPolicy : SchemePolicy() { val newPackageName = newPermissionInfo.packageName if (permissionTree != null && newPackageName != permissionTree.packageName) { Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + + LOG_TAG, + "Ignoring permission $permissionName declared in package" + " $newPackageName: base permission tree ${permissionTree.name} is" + " declared in another package ${permissionTree.packageName}" ) return@forEachIndexed } - val newPermission = if (oldPermission != null && - newPackageName != oldPermission.packageName) { - val oldPackageName = oldPermission.packageName - // Only allow system apps to redefine non-system permissions. - if (!packageState.isSystem) { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + - " $newPackageName: already declared in another package" + - " $oldPackageName" - ) - return@forEachIndexed - } - if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { - // It's a config permission and has no owner, take ownership now. - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) - } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) { - Slog.w( - LOG_TAG, "Overriding permission $permissionName with new declaration in" + - " system package $newPackageName: originally declared in another" + - " package $oldPackageName" - ) - // Remove permission state on owner change. - newState.externalState.userIds.forEachIndexed { _, userId -> - newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - setPermissionFlags(appId, userId, permissionName, 0) - } + val newPermission = + if (oldPermission != null && newPackageName != oldPermission.packageName) { + val oldPackageName = oldPermission.packageName + // Only allow system apps to redefine non-system permissions. + if (!packageState.isSystem) { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in package" + + " $newPackageName: already declared in another package" + + " $oldPackageName" + ) + return@forEachIndexed } - // Different from the old implementation, which removes the GIDs upon permission - // override, but adds them back on the next boot, we now just consistently keep - // the GIDs. - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId, - oldPermission.gids, oldPermission.areGidsPerUser - ) - } else { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in system package" + - " $newPackageName: already declared in another system package" + - " $oldPackageName" - ) - return@forEachIndexed - } - } else { - if (oldPermission != null && oldPermission.isReconciled) { - val isPermissionGroupChanged = newPermissionInfo.isRuntime && - newPermissionInfo.group != null && - newPermissionInfo.group != oldPermission.groupName - val isPermissionProtectionChanged = - oldPermission.type != Permission.TYPE_CONFIG && ( - (newPermissionInfo.isRuntime && !oldPermission.isRuntime) || - (newPermissionInfo.isInternal && !oldPermission.isInternal) + if ( + oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled + ) { + // It's a config permission and has no owner, take ownership now. + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId ) - if (isPermissionGroupChanged || isPermissionProtectionChanged) { + } else if ( + newState.externalState.packageStates[oldPackageName]?.isSystem != true + ) { + Slog.w( + LOG_TAG, + "Overriding permission $permissionName with new declaration in" + + " system package $newPackageName: originally declared in another" + + " package $oldPackageName" + ) + // Remove permission state on owner change. newState.externalState.userIds.forEachIndexed { _, userId -> newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - if (isPermissionGroupChanged) { - // We might auto-grant permissions if any permission of - // the group is already granted. Hence if the group of - // a granted permission changes we need to revoke it to - // avoid having permissions of the new group auto-granted. - Slog.w( - LOG_TAG, "Revoking runtime permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " group changed from ${oldPermission.groupName}" + - " to ${newPermissionInfo.group}" - ) - } - if (isPermissionProtectionChanged) { - Slog.w( - LOG_TAG, "Revoking permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " protection changed." - ) - } setPermissionFlags(appId, userId, permissionName, 0) } } + // Different from the old implementation, which removes the GIDs upon + // permission + // override, but adds them back on the next boot, we now just consistently + // keep + // the GIDs. + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId, + oldPermission.gids, + oldPermission.areGidsPerUser + ) + } else { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in system package" + + " $newPackageName: already declared in another system package" + + " $oldPackageName" + ) + return@forEachIndexed } - } - - // Different from the old implementation, which doesn't update the permission - // definition upon app update, but does update it on the next boot, we now - // consistently update the permission definition upon app update. - @Suppress("IfThenToElvis") - if (oldPermission != null) { - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) } else { - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId - ) + if (oldPermission != null && oldPermission.isReconciled) { + val isPermissionGroupChanged = + newPermissionInfo.isRuntime && + newPermissionInfo.group != null && + newPermissionInfo.group != oldPermission.groupName + val isPermissionProtectionChanged = + oldPermission.type != Permission.TYPE_CONFIG && + ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) || + (newPermissionInfo.isInternal && !oldPermission.isInternal)) + if (isPermissionGroupChanged || isPermissionProtectionChanged) { + newState.externalState.userIds.forEachIndexed { _, userId -> + newState.externalState.appIdPackageNames.forEachIndexed { + _, + appId, + _ -> + if (isPermissionGroupChanged) { + // We might auto-grant permissions if any permission of + // the group is already granted. Hence if the group of + // a granted permission changes we need to revoke it to + // avoid having permissions of the new group auto-granted. + Slog.w( + LOG_TAG, + "Revoking runtime permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " group changed from ${oldPermission.groupName}" + + " to ${newPermissionInfo.group}" + ) + } + if (isPermissionProtectionChanged) { + Slog.w( + LOG_TAG, + "Revoking permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " protection changed." + ) + } + setPermissionFlags(appId, userId, permissionName, 0) + } + } + } + } + + // Different from the old implementation, which doesn't update the permission + // definition upon app update, but does update it on the next boot, we now + // consistently update the permission definition upon app update. + @Suppress("IfThenToElvis") + if (oldPermission != null) { + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId + ) + } else { + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId + ) + } } - } if (parsedPermission.isTree) { newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission } else { newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission - val isPermissionChanged = oldPermission == null || - newPackageName != oldPermission.packageName || - newPermission.protectionLevel != oldPermission.protectionLevel || ( - oldPermission.isReconciled && ( - (newPermission.isSignature && isPackageSigningChanged) || ( - newPermission.isKnownSigner && - newPermission.knownCerts != oldPermission.knownCerts - ) || ( - newPermission.isRuntime && newPermission.groupName != null && - newPermission.groupName != oldPermission.groupName - ) - ) - ) + val isPermissionChanged = + oldPermission == null || + newPackageName != oldPermission.packageName || + newPermission.protectionLevel != oldPermission.protectionLevel || + (oldPermission.isReconciled && + ((newPermission.isSignature && isPackageSigningChanged) || + (newPermission.isKnownSigner && + newPermission.knownCerts != oldPermission.knownCerts) || + (newPermission.isRuntime && + newPermission.groupName != null && + newPermission.groupName != oldPermission.groupName))) if (isPermissionChanged) { changedPermissionNames += permissionName } @@ -552,39 +609,47 @@ class AppIdPermissionPolicy : SchemePolicy() { if (packageState != null && androidPackage == null) { return } - val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName] - ?.androidPackage + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage // Unlike in the previous implementation, we now also retain permission trees defined by // disabled system packages for consistency with permissions. newState.systemState.permissionTrees.forEachReversedIndexed { - permissionTreeIndex, permissionTreeName, permissionTree -> - if (permissionTree.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } != true - )) { + permissionTreeIndex, + permissionTreeName, + permissionTree -> + if ( + permissionTree.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + it.isTree && it.name == permissionTreeName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + it.isTree && it.name == permissionTreeName + } != true) + ) { newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex) } } newState.systemState.permissions.forEachReversedIndexed { - permissionIndex, permissionName, permission -> + permissionIndex, + permissionName, + permission -> val updatedPermission = updatePermissionIfDynamic(permission) - newState.mutateSystemState().mutatePermissions() + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, updatedPermission) - if (updatedPermission.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - !it.isTree && it.name == permissionName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - !it.isTree && it.name == permissionName - } != true - )) { + if ( + updatedPermission.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + !it.isTree && it.name == permissionName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + !it.isTree && it.name == permissionName + } != true) + ) { // Different from the old implementation where we keep the permission state if the // permission is declared by a disabled system package (ag/15189282), we now // shouldn't be notified when the updated system package is removed but the disabled @@ -608,9 +673,12 @@ class AppIdPermissionPolicy : SchemePolicy() { val permissionTree = findPermissionTree(permission.name) ?: return permission @Suppress("DEPRECATION") return permission.copy( - permissionInfo = PermissionInfo(permission.permissionInfo).apply { - packageName = permissionTree.packageName - }, appId = permissionTree.appId, isReconciled = true + permissionInfo = + PermissionInfo(permission.permissionInfo).apply { + packageName = permissionTree.packageName + }, + appId = permissionTree.appId, + isReconciled = true ) } @@ -636,8 +704,9 @@ class AppIdPermissionPolicy : SchemePolicy() { } private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) { - val hasOldPackage = appId in oldState.externalState.appIdPackageNames && - anyPackageInAppId(appId, oldState) { true } + val hasOldPackage = + appId in oldState.externalState.appIdPackageNames && + anyPackageInAppId(appId, oldState) { true } if (!hasOldPackage) { // Don't revoke anything if this isn't a package update, i.e. if information about the // old package isn't available. Notably, this also means skipping packages changed via @@ -650,46 +719,58 @@ class AppIdPermissionPolicy : SchemePolicy() { // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. val oldTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } val newTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q && - newTargetSdkVersion < Build.VERSION_CODES.Q + val isTargetSdkVersionDowngraded = + oldTargetSdkVersion >= Build.VERSION_CODES.Q && + newTargetSdkVersion < Build.VERSION_CODES.Q @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q && - newTargetSdkVersion >= Build.VERSION_CODES.Q - val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded && - !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage - val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage || - isTargetSdkVersionDowngraded + val isTargetSdkVersionUpgraded = + oldTargetSdkVersion < Build.VERSION_CODES.Q && + newTargetSdkVersion >= Build.VERSION_CODES.Q + val oldIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, oldState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val newIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, newState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val isNewlyRequestingLegacyExternalStorage = + !isTargetSdkVersionUpgraded && + !oldIsRequestLegacyExternalStorage && + newIsRequestLegacyExternalStorage + val shouldRevokeStorageAndMediaPermissions = + isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded if (shouldRevokeStorageAndMediaPermissions) { newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdPermissionFlags[appId]?.forEachReversedIndexed { - _, permissionName, oldFlags -> + _, + permissionName, + oldFlags -> // Do not revoke the permission during an upgrade if it's POLICY_FIXED or // SYSTEM_FIXED. Otherwise the user cannot grant back the permission. - if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS && - oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && - !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) { + if ( + permissionName in STORAGE_AND_MEDIA_PERMISSIONS && + oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && + !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK) + ) { Slog.v( - LOG_TAG, "Revoking storage permission: $permissionName for appId: " + + LOG_TAG, + "Revoking storage permission: $permissionName for appId: " + " $appId and user: $userId" ) - val newFlags = oldFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK - ) + val newFlags = + oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK) setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -704,9 +785,10 @@ class AppIdPermissionPolicy : SchemePolicy() { val externalState = newState.externalState externalState.userIds.forEachIndexed { _, userId -> externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - val isPermissionRequested = anyPackageInAppId(appId) { - permissionName in it.androidPackage!!.requestedPermissions - } + val isPermissionRequested = + anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions + } if (isPermissionRequested) { evaluatePermissionState(appId, userId, permissionName, installedPackageState) } @@ -720,7 +802,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { newState.externalState.userIds.forEachIndexed { _, userId -> evaluateAllPermissionStatesForPackageAndUser( - packageState, userId, installedPackageState + packageState, + userId, + installedPackageState ) } } @@ -732,7 +816,10 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { packageState.androidPackage?.requestedPermissions?.forEach { permissionName -> evaluatePermissionState( - packageState.appId, userId, permissionName, installedPackageState + packageState.appId, + userId, + permissionName, + installedPackageState ) } } @@ -779,57 +866,71 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) - val isRequestedByInstalledPackage = installedPackageState != null && - permissionName in installedPackageState.androidPackage!!.requestedPermissions + val isRequestedByInstalledPackage = + installedPackageState != null && + permissionName in + installedPackageState.androidPackage!!.requestedPermissions val isRequestedBySystemPackage = requestingPackageStates.anyIndexed { _, it -> it.isSystem } - val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it -> - isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) - } + val isCompatibilityPermission = + requestingPackageStates.anyIndexed { _, it -> + isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) + } // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform - var newFlags = if (!wasRevoked || isRequestedByInstalledPackage || - isRequestedBySystemPackage || isCompatibilityPermission) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } + var newFlags = + if ( + !wasRevoked || + isRequestedByInstalledPackage || + isRequestedBySystemPackage || + isCompatibilityPermission + ) { + PermissionFlags.INSTALL_GRANTED + } else { + PermissionFlags.INSTALL_REVOKED + } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) - var newFlags = if (hasMissingPackage && wasProtectionGranted) { - // Keep the non-runtime permission grants for shared UID with missing androidPackage - PermissionFlags.PROTECTION_GRANTED - } else { - val mayGrantByPrivileged = !permission.isPrivileged || - requestingPackageStates.anyIndexed { _, it -> - checkPrivilegedPermissionAllowlist(it, permission) - } - val shouldGrantBySignature = permission.isSignature && - requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionBySignature(it, permission) - } - val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionByProtectionFlags(it, permission) - } - if (mayGrantByPrivileged && - (shouldGrantBySignature || shouldGrantByProtectionFlags)) { + var newFlags = + if (hasMissingPackage && wasProtectionGranted) { + // Keep the non-runtime permission grants for shared UID with missing + // androidPackage PermissionFlags.PROTECTION_GRANTED } else { - 0 + val mayGrantByPrivileged = + !permission.isPrivileged || + requestingPackageStates.anyIndexed { _, it -> + checkPrivilegedPermissionAllowlist(it, permission) + } + val shouldGrantBySignature = + permission.isSignature && + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionBySignature(it, permission) + } + val shouldGrantByProtectionFlags = + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionByProtectionFlags(it, permission) + } + if ( + mayGrantByPrivileged && + (shouldGrantBySignature || shouldGrantByProtectionFlags) + ) { + PermissionFlags.PROTECTION_GRANTED + } else { + 0 + } } - } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } // Different from the old implementation, which seemingly allows granting an // unallowlisted privileged permission via development or role but revokes it upon next @@ -840,9 +941,9 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED) } if (permission.isRole) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)) } setPermissionFlags(appId, userId, permissionName, newFlags) } else if (permission.isRuntime) { @@ -850,7 +951,9 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags) val targetSdkVersion = requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) { - targetSdkVersion, _, packageState -> + targetSdkVersion, + _, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } if (targetSdkVersion < Build.VERSION_CODES.M) { @@ -883,23 +986,27 @@ class AppIdPermissionPolicy : SchemePolicy() { } } val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED) - val isLeanbackNotificationsPermission = newState.externalState.isLeanback && - permissionName in NOTIFICATIONS_PERMISSIONS - val isImplicitPermission = requestingPackageStates.anyIndexed { _, it -> - permissionName in it.androidPackage!!.implicitPermissions - } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[permissionName] - val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed { - _, sourcePermissionName -> - val sourcePermission = newState.systemState.permissions[sourcePermissionName] - checkNotNull(sourcePermission) { - "Unknown source permission $sourcePermissionName in split permissions" + val isLeanbackNotificationsPermission = + newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS + val isImplicitPermission = + requestingPackageStates.anyIndexed { _, it -> + permissionName in it.androidPackage!!.implicitPermissions + } + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[permissionName] + val isAnySourcePermissionNonRuntime = + sourcePermissions?.anyIndexed { _, sourcePermissionName -> + val sourcePermission = + newState.systemState.permissions[sourcePermissionName] + checkNotNull(sourcePermission) { + "Unknown source permission $sourcePermissionName in split permissions" + } + !sourcePermission.isRuntime } - !sourcePermission.isRuntime - } ?: false - val shouldGrantByImplicit = isLeanbackNotificationsPermission || - (isImplicitPermission && isAnySourcePermissionNonRuntime) + ?: false + val shouldGrantByImplicit = + isLeanbackNotificationsPermission || + (isImplicitPermission && isAnySourcePermissionNonRuntime) if (shouldGrantByImplicit) { newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED if (wasRevoked) { @@ -907,26 +1014,31 @@ class AppIdPermissionPolicy : SchemePolicy() { } } else { newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED - if ((wasGrantedByLegacy || wasGrantedByImplicit) && - newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) { + if ( + (wasGrantedByLegacy || wasGrantedByImplicit) && + newFlags.hasBits(PermissionFlags.APP_OP_REVOKED) + ) { // The permission was granted from a compatibility grant or an implicit // grant, however this flag might still be set if the user denied this // permission in the settings. Hence upon app upgrade and when this // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke // the permission, we want to remove this flag so that the app can request // the permission again. - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED) } } if (!isImplicitPermission && hasImplicitFlag) { newFlags = newFlags andInv PermissionFlags.IMPLICIT var shouldRetainAsNearbyDevices = false if (permissionName in NEARBY_DEVICES_PERMISSIONS) { - val accessBackgroundLocationFlags = getPermissionFlags( - appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION - ) + val accessBackgroundLocationFlags = + getPermissionFlags( + appId, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) shouldRetainAsNearbyDevices = PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) && !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT) @@ -937,46 +1049,57 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED } } else { - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or - PermissionFlags.USER_FIXED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED) } } } val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED) - val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { - // All restricted permissions start as exempt. If there's an installer for the - // package, we will drop this UPGRADE_EXEMPT flag when we receive the - // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. - // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that - // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to - // apps with missing permission state. - // This way we make sure both pre-installed apps, and apps updated/installed after - // a rollback snapshot is taken, can get the allowlist for permissions that won't be - // allowlisted otherwise. - newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT - true - } else { - wasExempt - } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + val isExempt = + if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { + // All restricted permissions start as exempt. If there's an installer for the + // package, we will drop this UPGRADE_EXEMPT flag when we receive the + // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. + // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag + // that + // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, + // and to + // apps with missing permission state. + // This way we make sure both pre-installed apps, and apps updated/installed + // after + // a rollback snapshot is taken, can get the allowlist for permissions that + // won't be + // allowlisted otherwise. + newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT + true + } else { + wasExempt + } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } else { - Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + - "for permission ${permission.name} while evaluating permission state" + - "for appId $appId and userId $userId") + Slog.e( + LOG_TAG, + "Unknown protection level ${permission.protectionLevel}" + + "for permission ${permission.name} while evaluating permission state" + + "for appId $appId and userId $userId" + ) } } @@ -985,7 +1108,7 @@ class AppIdPermissionPolicy : SchemePolicy() { forEachPackageInAppId(appId) { implicitPermissions += it.androidPackage!!.implicitPermissions } - implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName -> + implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName -> val implicitPermission = newState.systemState.permissions[implicitPermissionName] checkNotNull(implicitPermission) { "Unknown implicit permission $implicitPermissionName in split permissions" @@ -999,10 +1122,11 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!isNewPermission) { return@implicitPermissions } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[implicitPermissionName] + ?: return@implicitPermissions var newFlags = getPermissionFlags(appId, userId, implicitPermissionName) - sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName -> + sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName -> val sourcePermission = newState.systemState.permissions[sourcePermissionName] checkNotNull(sourcePermission) { "Unknown source permission $sourcePermissionName in split permissions" @@ -1032,11 +1156,14 @@ class AppIdPermissionPolicy : SchemePolicy() { permissionName: String ): Boolean { for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) { - if (compatibilityPermission.name == permissionName && - androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) { + if ( + compatibilityPermission.name == permissionName && + androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion + ) { Slog.i( - LOG_TAG, "Auto-granting $permissionName to old package" + - " ${androidPackage.packageName}" + LOG_TAG, + "Auto-granting $permissionName to old package" + + " ${androidPackage.packageName}" ) return true } @@ -1058,15 +1185,23 @@ class AppIdPermissionPolicy : SchemePolicy() { // and the defining package still trusts the old certificate for permissions // - or it shares the above relationships with the system package val packageSigningDetails = packageState.androidPackage!!.signingDetails - val sourceSigningDetails = newState.externalState - .packageStates[permission.packageName]?.androidPackage?.signingDetails - val platformSigningDetails = newState.externalState - .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) == true || + val sourceSigningDetails = + newState.externalState.packageStates[permission.packageName] + ?.androidPackage + ?.signingDetails + val platformSigningDetails = + newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! + .androidPackage!! + .signingDetails + return sourceSigningDetails?.hasCommonSignerWithCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) == true || packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( @@ -1082,8 +1217,9 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!(packageState.isSystem && packageState.isPrivileged)) { return true } - if (permission.packageName !in - newState.externalState.privilegedPermissionAllowlistPackages) { + if ( + permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages + ) { return true } val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name) @@ -1099,13 +1235,15 @@ class AppIdPermissionPolicy : SchemePolicy() { // Apps that are in updated apex's do not need to be allowlisted if (!packageState.isApkInUpdatedApex) { Slog.w( - LOG_TAG, "Privileged permission ${permission.name} for package" + - " ${packageState.packageName} (${packageState.path}) not in" + - " privileged permission allowlist" + LOG_TAG, + "Privileged permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " privileged permission allowlist" ) if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - privilegedPermissionAllowlistViolations += "${packageState.packageName}" + - " (${packageState.path}): ${permission.name}" + privilegedPermissionAllowlistViolations += + "${packageState.packageName}" + + " (${packageState.path}): ${permission.name}" } } } @@ -1124,32 +1262,40 @@ class AppIdPermissionPolicy : SchemePolicy() { val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { - packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( - packageName, permissionName - ) - packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState( - packageName, permissionName - ) + packageState.isVendor -> + permissionAllowlist.getVendorPrivilegedAppAllowlistState( + packageName, + permissionName + ) + packageState.isProduct -> + permissionAllowlist.getProductPrivilegedAppAllowlistState( + packageName, + permissionName + ) packageState.isSystemExt -> permissionAllowlist.getSystemExtPrivilegedAppAllowlistState( - packageName, permissionName + packageName, + permissionName ) apexModuleName != null -> { - val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState( - packageName, permissionName - ) + val nonApexAllowlistState = + permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) if (nonApexAllowlistState != null) { // TODO(andreionea): Remove check as soon as all apk-in-apex // permission allowlists are migrated. Slog.w( - LOG_TAG, "Package $packageName is an APK in APEX but has permission" + + LOG_TAG, + "Package $packageName is an APK in APEX but has permission" + " allowlist on the system image, please bundle the allowlist in the" + " $apexModuleName APEX instead" ) } - val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState( - apexModuleName, packageName, permissionName - ) + val apexAllowlistState = + permissionAllowlist.getApexPrivilegedAppAllowlistState( + apexModuleName, + packageName, + permissionName + ) apexAllowlistState ?: nonApexAllowlistState } else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) @@ -1208,18 +1354,19 @@ class AppIdPermissionPolicy : SchemePolicy() { val knownPackages = newState.externalState.knownPackages val packageName = packageState.packageName if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) { - val shouldGrant = if (packageState.isUpdatedSystemApp) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - val disabledSystemPackageState = newState.externalState - .disabledSystemPackageStates[packageState.packageName] - val disabledSystemPackage = disabledSystemPackageState?.androidPackage - disabledSystemPackage != null && - permission.name in disabledSystemPackage.requestedPermissions && - shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) - } else { - shouldGrantPrivilegedOrOemPermission(packageState, permission) - } + val shouldGrant = + if (packageState.isUpdatedSystemApp) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + val disabledSystemPackageState = + newState.externalState.disabledSystemPackageStates[packageState.packageName] + val disabledSystemPackage = disabledSystemPackageState?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions && + shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) + } else { + shouldGrantPrivilegedOrOemPermission(packageState, permission) + } if (shouldGrant) { return true } @@ -1230,16 +1377,18 @@ class AppIdPermissionPolicy : SchemePolicy() { // we still want to blindly grant it to old apps. return true } - if (permission.isInstaller && ( - packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || - packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!! - )) { + if ( + permission.isInstaller && + (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || + packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!) + ) { // If this permission is to be granted to the system installer and // this app is an installer or permission controller, then it gets the permission. return true } - if (permission.isVerifier && - packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) { + if ( + permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!! + ) { // If this permission is to be granted to the system verifier and // this app is a verifier, then it gets the permission. return true @@ -1248,53 +1397,67 @@ class AppIdPermissionPolicy : SchemePolicy() { // Any pre-installed system app is allowed to get this permission. return true } - if (permission.isKnownSigner && - androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) { + if ( + permission.isKnownSigner && + androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts) + ) { // If the permission is to be granted to a known signer then check if any of this // app's signing certificates are in the trusted certificate digest Set. return true } - if (permission.isSetup && - packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) { + if ( + permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!! + ) { // If this permission is to be granted to the system setup wizard and // this app is a setup wizard, then it gets the permission. return true } - if (permission.isSystemTextClassifier && - packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) { + if ( + permission.isSystemTextClassifier && + packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!! + ) { // Special permissions for the system default text classifier. return true } - if (permission.isConfigurator && - packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) { + if ( + permission.isConfigurator && + packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!! + ) { // Special permissions for the device configurator. return true } - if (permission.isIncidentReportApprover && - packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) { + if ( + permission.isIncidentReportApprover && + packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!! + ) { // If this permission is to be granted to the incident report approver and // this app is the incident report approver, then it gets the permission. return true } - if (permission.isAppPredictor && - packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) { + if ( + permission.isAppPredictor && + packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!! + ) { // Special permissions for the system app predictor. return true } - if (permission.isCompanion && - packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) { + if ( + permission.isCompanion && + packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!! + ) { // Special permissions for the system companion device manager. return true } - if (permission.isRetailDemo && - packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) { + if ( + permission.isRetailDemo && + packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!! + ) { // Special permission granted only to the OEM specified retail demo app. // Note that the original code was passing app ID as UID, so this behavior is kept // unchanged. return true } - if (permission.isRecents && - packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { + if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { // Special permission for the recents app. return true } @@ -1319,9 +1482,10 @@ class AppIdPermissionPolicy : SchemePolicy() { // flag. if (packageState.isVendor && !permission.isVendorPrivileged) { Slog.w( - LOG_TAG, "Permission $permissionName cannot be granted to privileged" + - " vendor app $packageName because it isn't a vendorPrivileged" + - " permission" + LOG_TAG, + "Permission $permissionName cannot be granted to privileged" + + " vendor app $packageName because it isn't a vendorPrivileged" + + " permission" ) return false } @@ -1330,8 +1494,11 @@ class AppIdPermissionPolicy : SchemePolicy() { } permission.isOem -> { if (packageState.isOem) { - val allowlistState = newState.externalState.permissionAllowlist - .getOemAppAllowlistState(packageName, permissionName) + val allowlistState = + newState.externalState.permissionAllowlist.getOemAppAllowlistState( + packageName, + permissionName + ) checkNotNull(allowlistState) { "OEM permission $permissionName requested by package" + " $packageName must be explicitly declared granted or not" @@ -1358,13 +1525,18 @@ class AppIdPermissionPolicy : SchemePolicy() { val appId = externalState.packageStates[packageName]?.appId ?: continue newState.userStates.forEachIndexed { _, userId, _ -> evaluatePermissionState( - appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null + appId, + userId, + Manifest.permission.PACKAGE_USAGE_STATS, + null ) } } if (!privilegedPermissionAllowlistViolations.isEmpty()) { - throw IllegalStateException("Signature|privileged permissions not in privileged" + - " permission allowlist: $privilegedPermissionAllowlistViolations") + throw IllegalStateException( + "Signature|privileged permissions not in privileged" + + " permission allowlist: $privilegedPermissionAllowlistViolations" + ) } } @@ -1389,10 +1561,14 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.findPermissionTree(permissionName: String): Permission? = state.systemState.permissionTrees.firstNotNullOfOrNullIndexed { - _, permissionTreeName, permissionTree -> - if (permissionName.startsWith(permissionTreeName) && - permissionName.length > permissionTreeName.length && - permissionName[permissionTreeName.length] == '.') { + _, + permissionTreeName, + permissionTree -> + if ( + permissionName.startsWith(permissionTreeName) && + permissionName.length > permissionTreeName.length && + permissionName[permissionTreeName.length] == '.' + ) { permissionTree } else { null @@ -1403,15 +1579,11 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission } - /** - * returns all permission group definitions available in the system - */ + /** returns all permission group definitions available in the system */ fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> = state.systemState.permissionGroups - /** - * returns all permission definitions available in the system - */ + /** returns all permission definitions available in the system */ fun GetStateScope.getPermissions(): IndexedMap<String, Permission> = state.systemState.permissions @@ -1430,11 +1602,8 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? = state.userStates[userId]?.appIdPermissionFlags?.get(appId) - fun GetStateScope.getPermissionFlags( - appId: Int, - userId: Int, - permissionName: String - ): Int = getPermissionFlags(state, appId, userId, permissionName) + fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + getPermissionFlags(state, appId, userId, permissionName) private fun MutateStateScope.getOldStatePermissionFlags( appId: Int, @@ -1465,8 +1634,10 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { - val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId] - .getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdPermissionFlags[appId] + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false @@ -1517,38 +1688,37 @@ class AppIdPermissionPolicy : SchemePolicy() { private const val PLATFORM_PACKAGE_NAME = "android" // A set of permissions that we don't want to revoke when they are no longer implicit. - private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.ACTIVITY_RECOGNITION, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - ) - - private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf( - Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_CONNECT, - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.NEARBY_WIFI_DEVICES - ) + private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + ) - private val NOTIFICATIONS_PERMISSIONS = indexedSetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NEARBY_DEVICES_PERMISSIONS = + indexedSetOf( + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.NEARBY_WIFI_DEVICES + ) - private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS) + + private val STORAGE_AND_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) - /** - * Mask for all permission flags that can be set by the user - */ + /** Mask for all permission flags that can be set by the user */ private const val USER_SETTABLE_MASK = PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or @@ -1558,16 +1728,14 @@ class AppIdPermissionPolicy : SchemePolicy() { PermissionFlags.USER_SELECTED /** - * Mask for all permission flags that imply we shouldn't automatically modify the - * permission grant state. + * Mask for all permission flags that imply we shouldn't automatically modify the permission + * grant state. */ private const val SYSTEM_OR_POLICY_FIXED_MASK = PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED } - /** - * Listener for permission flags changes. - */ + /** Listener for permission flags changes. */ abstract class OnPermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt index b644d8fe7388..edacda03f277 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt @@ -32,7 +32,6 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { * Upgrade the package permissions, if needed. * * @param version package version - * * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion] */ fun MutateStateScope.upgradePackageState( @@ -43,7 +42,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { val packageName = packageState.packageName if (version <= 3) { Slog.v( - LOG_TAG, "Allowlisting and upgrading background location permission for " + + LOG_TAG, + "Allowlisting and upgrading background location permission for " + "package: $packageName, version: $version, user:$userId" ) allowlistRestrictedPermissions(packageState, userId) @@ -51,7 +51,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } if (version <= 10) { Slog.v( - LOG_TAG, "Upgrading access media location permission for package: $packageName" + + LOG_TAG, + "Upgrading access media location permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeAccessMediaLocationPermission(packageState, userId) @@ -59,7 +60,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastT check, when moving subsystem to mainline. if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) { Slog.v( - LOG_TAG, "Upgrading scoped permissions for package: $packageName" + + LOG_TAG, + "Upgrading scoped permissions for package: $packageName" + ", version: $version, user: $userId" ) upgradeAuralVisualMediaPermissions(packageState, userId) @@ -67,7 +69,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastU check, when moving subsystem to mainline. if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) { Slog.v( - LOG_TAG, "Upgrading visual media permission for package: $packageName" + + LOG_TAG, + "Upgrading visual media permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeUserSelectedVisualMediaPermission(packageState, userId) @@ -84,8 +87,11 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) { with(policy) { updatePermissionFlags( - packageState.appId, userId, permissionName, - PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT + packageState.appId, + userId, + permissionName, + PermissionFlags.UPGRADE_EXEMPT, + PermissionFlags.UPGRADE_EXEMPT ) } } @@ -96,21 +102,27 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in - packageState.androidPackage!!.requestedPermissions) { + if ( + Manifest.permission.ACCESS_BACKGROUND_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { val appId = packageState.appId - val accessFineLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) - } - val accessCoarseLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) - } + val accessFineLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) + } + val accessCoarseLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) + } val isForegroundLocationGranted = PermissionFlags.isAppOpGranted(accessFineLocationFlags) || PermissionFlags.isAppOpGranted(accessCoarseLocationFlags) if (isForegroundLocationGranted) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION ) } } @@ -120,24 +132,29 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_MEDIA_LOCATION in - packageState.androidPackage!!.requestedPermissions) { - val flags = with(policy) { - getPermissionFlags( - packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE - ) - } + if ( + Manifest.permission.ACCESS_MEDIA_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { + val flags = + with(policy) { + getPermissionFlags( + packageState.appId, + userId, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + } if (PermissionFlags.isAppOpGranted(flags)) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_MEDIA_LOCATION ) } } } - /** - * Upgrade permissions based on storage permissions grant - */ + /** Upgrade permissions based on storage permissions grant */ private fun MutateStateScope.upgradeAuralVisualMediaPermissions( packageState: PackageState, userId: Int @@ -147,15 +164,15 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isStorageUserGranted = + STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isStorageUserGranted) { AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName -> if (permissionName in requestedPermissionNames) { @@ -165,9 +182,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } } - /** - * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] - */ + /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */ private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission( packageState: PackageState, userId: Int @@ -177,19 +192,21 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isVisualMediaUserGranted = + VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isVisualMediaUserGranted) { if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) { grantRuntimePermission( - packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + packageState, + userId, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) } } @@ -201,7 +218,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { permissionName: String ) { Slog.v( - LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " + + LOG_TAG, + "Granting runtime permission for package: ${packageState.packageName}, " + "permission: $permissionName, userId: $userId" ) val permission = newState.systemState.permissions[permissionName]!! @@ -220,13 +238,13 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } flags = flags or PermissionFlags.RUNTIME_GRANTED - flags = flags andInv ( - PermissionFlags.APP_OP_REVOKED or - PermissionFlags.IMPLICIT or - PermissionFlags.LEGACY_GRANTED or - PermissionFlags.HIBERNATION or - PermissionFlags.ONE_TIME - ) + flags = + flags andInv + (PermissionFlags.APP_OP_REVOKED or + PermissionFlags.IMPLICIT or + PermissionFlags.LEGACY_GRANTED or + PermissionFlags.HIBERNATION or + PermissionFlags.ONE_TIME) with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -234,39 +252,45 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName private const val MASK_ANY_FIXED = - PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or - PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.SYSTEM_FIXED - private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_BACKGROUND_LOCATION, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.RECEIVE_WAP_PUSH, - Manifest.permission.RECEIVE_MMS, - Manifest.permission.READ_CELL_BROADCASTS, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.PROCESS_OUTGOING_CALLS - ) + private val LEGACY_RESTRICTED_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_BACKGROUND_LOCATION, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS, + Manifest.permission.READ_CELL_BROADCASTS, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.PROCESS_OUTGOING_CALLS + ) - private val STORAGE_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val STORAGE_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + private val AURAL_VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) // Visual media permissions in T - private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION - ) + private val VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt index 37a4a90f8f80..1bee356dfbf3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt @@ -135,9 +135,7 @@ class DevicePermissionPersistence { ) { tag(TAG_DEVICE) { attributeInterned(ATTR_ID, deviceId) - permissionFlags.forEachIndexed { _, name, flags -> - serializePermission(name, flags) - } + permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) } } } @@ -145,11 +143,12 @@ class DevicePermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } @@ -166,4 +165,4 @@ class DevicePermissionPersistence { private const val ATTR_NAME = "name" private const val ATTR_FLAGS = "flags" } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 4addab3cd424..15a58593432e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -53,8 +53,8 @@ class DevicePermissionPolicy : SchemePolicy() { override fun MutateStateScope.onAppIdRemoved(appId: Int) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (appId in userState.appIdDevicePermissionFlags) { - newState.mutateUserStateAt(userStateIndex) - .mutateAppIdDevicePermissionFlags() -= appId + newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -= + appId } } } @@ -96,10 +96,11 @@ class DevicePermissionPolicy : SchemePolicy() { val appId = packageState.appId val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags androidPackage.requestedPermissions.forEach { permissionName -> - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -116,7 +117,9 @@ class DevicePermissionPolicy : SchemePolicy() { } newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { - _, deviceId, permissionFlags -> + _, + deviceId, + permissionFlags -> permissionFlags.forEachReversedIndexed { _, permissionName, _ -> if (permissionName !in requestedPermissions) { setPermissionFlags(appId, deviceId, userId, permissionName, 0) @@ -166,11 +169,17 @@ class DevicePermissionPolicy : SchemePolicy() { userId: Int, permissionName: String ): Int { - val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) - ?.getWithDefault(permissionName, 0) ?: 0 + val flags = + state.userStates[userId] + ?.appIdDevicePermissionFlags + ?.get(appId) + ?.get(deviceId) + ?.getWithDefault(permissionName, 0) + ?: 0 if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," + + LOG_TAG, + "getPermissionFlags: appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " flags=${PermissionFlags.toString(flags)}" ) @@ -186,7 +195,12 @@ class DevicePermissionPolicy : SchemePolicy() { flags: Int ): Boolean = updatePermissionFlags( - appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags + appId, + deviceId, + userId, + permissionName, + PermissionFlags.MASK_ALL, + flags ) private fun MutateStateScope.updatePermissionFlags( @@ -201,20 +215,23 @@ class DevicePermissionPolicy : SchemePolicy() { Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") return false } - val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] - ?.get(deviceId).getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdDevicePermissionFlags[appId] + ?.get(deviceId) + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false } val appIdDevicePermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() - val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { - MutableIndexedReferenceMap() - } + val devicePermissionFlags = + appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() } if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," + + LOG_TAG, + "setPermissionFlags(): appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " newFlags=${PermissionFlags.toString(newFlags)}" ) @@ -229,40 +246,39 @@ class DevicePermissionPolicy : SchemePolicy() { } listeners.forEachIndexed { _, it -> it.onDevicePermissionFlagsChanged( - appId, userId, deviceId, permissionName, oldFlags, newFlags + appId, + userId, + deviceId, + permissionName, + oldFlags, + newFlags ) } return true } fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners + listener - } + synchronized(listenersLock) { listeners = listeners + listener } } fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners - listener - } + synchronized(listenersLock) { listeners = listeners - listener } } private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_AWARE_PERMISSIONS.contains(permissionName) + DEVICE_AWARE_PERMISSIONS.contains(permissionName) companion object { private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - /** - * These permissions are supported for virtual devices. - */ + /** These permissions are supported for virtual devices. */ // TODO: b/298661870 - Use new API to get the list of device aware permissions. val DEVICE_AWARE_PERMISSIONS = emptySet<String>() } /** - * TODO: b/289355341 - implement listener for permission changes - * Listener for permission flags changes. + * TODO: b/289355341 - implement listener for permission changes Listener for permission flags + * changes. */ abstract class OnDevicePermissionFlagsChangedListener { /** @@ -288,4 +304,4 @@ class DevicePermissionPolicy : SchemePolicy() { */ abstract fun onStateMutated() } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt index c7fe1a9e744e..aa569280eadf 100644 --- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt +++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt @@ -26,8 +26,7 @@ data class Permission( val isReconciled: Boolean, val type: Int, val appId: Int, - @Suppress("ArrayInDataClass") - val gids: IntArray = EmptyArray.INT, + @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT, val areGidsPerUser: Boolean = false ) { inline val name: String @@ -43,8 +42,7 @@ data class Permission( get() = type == TYPE_DYNAMIC inline val protectionLevel: Int - @Suppress("DEPRECATION") - get() = permissionInfo.protectionLevel + @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel inline val protection: Int get() = permissionInfo.protection diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 550d1480fc81..b9d89c2184b7 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -32,15 +32,12 @@ import com.android.server.permission.access.util.hasBits * * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so * that: - * * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package * `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing * edge cases during module rollbacks. - * * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and * implicit permissions split from non-runtime permissions are never revoked, without checking * split permissions and package state everywhere slowly and in slightly different ways. - * * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and * potentially re-granting permissions upon restriction state changes. * @@ -55,9 +52,7 @@ import com.android.server.permission.access.util.hasBits * don't have any effect on the binary permission state. */ object PermissionFlags { - /** - * Permission flag for a normal permission that is granted at package installation. - */ + /** Permission flag for a normal permission that is granted at package installation. */ const val INSTALL_GRANTED = 1 shl 0 /** @@ -97,8 +92,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission whose state is set by the user. * - * For example, this flag may be set when the permission is allowed by the user in the - * request permission dialog, or managed in the permission settings. + * For example, this flag may be set when the permission is allowed by the user in the request + * permission dialog, or managed in the permission settings. * * @see PackageManager.FLAG_PERMISSION_USER_SET */ @@ -290,8 +285,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission that is selected by the user. * - * For example, this flag may be set when one of the coarse/fine location accuracies is - * selected by the user. + * For example, this flag may be set when one of the coarse/fine location accuracies is selected + * by the user. * * This flag is informational and managed by PermissionController. * @@ -299,28 +294,37 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 - /** - * Mask for all permission flags. - */ + /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() - /** - * Mask for all permission flags that may be applied to a runtime permission. - */ - const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or - SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or - USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or - SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or - APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED - - /** - * Mask for all permission flags about permission exemption. - */ + /** Mask for all permission flags that may be applied to a runtime permission. */ + const val MASK_RUNTIME = + ROLE or + RUNTIME_GRANTED or + USER_SET or + USER_FIXED or + POLICY_FIXED or + SYSTEM_FIXED or + PREGRANT or + LEGACY_GRANTED or + IMPLICIT_GRANTED or + IMPLICIT or + USER_SENSITIVE_WHEN_GRANTED or + USER_SENSITIVE_WHEN_REVOKED or + INSTALLER_EXEMPT or + SYSTEM_EXEMPT or + UPGRADE_EXEMPT or + RESTRICTION_REVOKED or + SOFT_RESTRICTED or + APP_OP_REVOKED or + ONE_TIME or + HIBERNATION or + USER_SELECTED + + /** Mask for all permission flags about permission exemption. */ const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT - /** - * Mask for all permission flags about permission restriction. - */ + /** Mask for all permission flags about permission restriction. */ const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { @@ -363,11 +367,13 @@ object PermissionFlags { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT } if (flags.hasBits(IMPLICIT)) { - apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) { - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - } else { - PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - } + apiFlags = + apiFlags or + if (flags.hasBits(LEGACY_GRANTED)) { + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + } else { + PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED + } } if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED @@ -440,8 +446,10 @@ object PermissionFlags { } flags = flags or (oldFlags and LEGACY_GRANTED) flags = flags or (oldFlags and IMPLICIT_GRANTED) - if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || - apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) { + if ( + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) + ) { flags = flags or IMPLICIT } if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 2a292655317e..ab3d78c9958c 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -41,10 +41,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.os.UserHandle import android.os.UserManager -import android.permission.flags.Flags import android.permission.IOnPermissionsChangeListener import android.permission.PermissionControllerManager import android.permission.PermissionManager +import android.permission.flags.Flags import android.provider.Settings import android.util.ArrayMap import android.util.ArraySet @@ -88,28 +88,25 @@ import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission -import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState +import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.policy.SoftRestrictedPermissionPolicy -import libcore.util.EmptyArray import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import libcore.util.EmptyArray -/** - * Modern implementation of [PermissionManagerServiceInterface]. - */ -class PermissionService( - private val service: AccessCheckingService -) : PermissionManagerServiceInterface { +/** Modern implementation of [PermissionManagerServiceInterface]. */ +class PermissionService(private val service: AccessCheckingService) : + PermissionManagerServiceInterface { private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy @@ -131,8 +128,7 @@ class PermissionService( private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener private val storageVolumeLock = Any() - @GuardedBy("storageVolumeLock") - private val mountedStorageVolumes = ArraySet<String?>() + @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>() @GuardedBy("storageVolumeLock") private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>() @@ -144,8 +140,8 @@ class PermissionService( * A permission backup might contain apps that are not installed. In this case we delay the * restoration until the app is installed. * - * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where - * there is **no more** delayed backup left. + * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no + * more** delayed backup left. */ private val isDelayedPermissionBackupFinished = SparseBooleanArray() @@ -154,9 +150,10 @@ class PermissionService( packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java) packageManagerLocal = LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java) - platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) - ) + platformCompat = + IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) + ) systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() @@ -166,8 +163,8 @@ class PermissionService( PackageManager.invalidatePackageInfoCache() PermissionManager.disablePackageNamePermissionCache() - handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true) - .apply { start() } + handlerThread = + ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() } handler = Handler(handlerThread.looper) onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper) onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener() @@ -181,9 +178,7 @@ class PermissionService( return emptyList() } - val permissionGroups = service.getState { - with(policy) { getPermissionGroups() } - } + val permissionGroups = service.getState { with(policy) { getPermissionGroups() } } return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup -> if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { @@ -206,9 +201,9 @@ class PermissionService( return null } - permissionGroup = service.getState { - with(policy) { getPermissionGroups()[permissionGroupName] } - } ?: return null + permissionGroup = + service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { return null @@ -242,29 +237,28 @@ class PermissionService( return null } - permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) { return null } val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage - targetSdkVersion = when { - // System sees all flags. - isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT - opPackage != null -> opPackage.targetSdkVersion - else -> Build.VERSION_CODES.CUR_DEVELOPMENT - } + targetSdkVersion = + when { + // System sees all flags. + isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT + opPackage != null -> opPackage.targetSdkVersion + else -> Build.VERSION_CODES.CUR_DEVELOPMENT + } } return permission.generatePermissionInfo(flags, targetSdkVersion) } - /** - * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. - */ + /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */ private fun Permission.generatePermissionInfo( flags: Int, targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT @@ -296,22 +290,27 @@ class PermissionService( return null } - val permissions = service.getState { - if (permissionGroupName != null) { - val permissionGroup = - with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null + val permissions = + service.getState { + if (permissionGroupName != null) { + val permissionGroup = + with(policy) { getPermissionGroups()[permissionGroupName] } + ?: return null - if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { - return null + if ( + !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid) + ) { + return null + } } - } - with(policy) { getPermissions() } - } + with(policy) { getPermissions() } + } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> - if (permission.groupName == permissionGroupName && - snapshot.isPackageVisibleToUid(permission.packageName, callingUid) + if ( + permission.groupName == permissionGroupName && + snapshot.isPackageVisibleToUid(permission.packageName, callingUid) ) { permission.generatePermissionInfo(flags) } else { @@ -334,9 +333,7 @@ class PermissionService( private inline fun getPermissionsWithProtectionOrProtectionFlags( predicate: (Permission) -> Boolean ): List<PermissionInfo> { - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> if (predicate(permission)) { @@ -348,18 +345,16 @@ class PermissionService( } override fun getPermissionGids(permissionName: String, userId: Int): IntArray { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return EmptyArray.INT + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return EmptyArray.INT return permission.getGidsForUser(userId) } override fun getInstalledPermissions(packageName: String): Set<String> { requireNotNull(packageName) { "packageName cannot be null" } - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission -> if (permission.packageName == packageName) { @@ -398,9 +393,8 @@ class PermissionService( permissionInfo.protectionLevel = PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel) - val newPermission = Permission( - permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId - ) + val newPermission = + Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId) with(policy) { addPermission(newPermission, !async) } } @@ -431,7 +425,7 @@ class PermissionService( val callingUid = Binder.getCallingUid() val permissionTree = with(policy) { findPermissionTree(permissionName) } if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) { - return permissionTree + return permissionTree } throw SecurityException( @@ -447,8 +441,9 @@ class PermissionService( // if that plus the size of 'info' would exceed our stated maximum. if (permissionTree.appId != Process.SYSTEM_UID) { val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree) - if (permissionTreeFootprint + permissionInfo.calculateFootprint() > - MAX_PERMISSION_TREE_FOOTPRINT + if ( + permissionTreeFootprint + permissionInfo.calculateFootprint() > + MAX_PERMISSION_TREE_FOOTPRINT ) { throw SecurityException("Permission tree size cap exceeded") } @@ -483,14 +478,16 @@ class PermissionService( packageManagerInternal.getPackageStateInternal(androidPackage.packageName) if (packageState == null) { Slog.e( - LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" + + LOG_TAG, + "checkUidPermission: PackageState not found for AndroidPackage" + " $androidPackage" ) return PackageManager.PERMISSION_DENIED } - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { + isPermissionGranted(packageState, userId, permissionName, deviceId) + } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -505,9 +502,7 @@ class PermissionService( } } - /** - * Internal implementation that should only be called by [checkUidPermission]. - */ + /** Internal implementation that should only be called by [checkUidPermission]. */ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean { val uidPermissions = systemConfig.systemPermissions[uid] ?: return false if (permissionName in uidPermissions) { @@ -532,12 +527,14 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return PackageManager.PERMISSION_DENIED - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -566,8 +563,15 @@ class PermissionService( } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] - if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) + if ( + fullerPermissionName != null && + isSinglePermissionGranted( + appId, + userId, + isInstantApp, + fullerPermissionName, + deviceId + ) ) { return true } @@ -575,9 +579,7 @@ class PermissionService( return false } - /** - * Internal implementation that should only be called by [isPermissionGranted]. - */ + /** Internal implementation that should only be called by [isPermissionGranted]. */ private fun GetStateScope.isSinglePermissionGranted( appId: Int, userId: Int, @@ -604,20 +606,27 @@ class PermissionService( requireNotNull(packageName) { "packageName cannot be null" } Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") return emptySet() } service.getState { - val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) } - ?: return emptySet() + val permissionFlags = + with(policy) { getUidPermissionFlags(packageState.appId, userId) } + ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { + if ( + isPermissionGranted( + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT + ) + ) { permissionName } else { null @@ -635,8 +644,8 @@ class PermissionService( // permission state is not found, now we always return at least global GIDs. This is // more consistent with the pre-S-refactor behavior. This is also because we are now // actively trimming the per-UID objects when empty. - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return globalGids.copyOf() + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf() val gids = GrowingIntArray.wrap(globalGids) permissionFlags.forEachIndexed { _, permissionName, flags -> @@ -644,8 +653,8 @@ class PermissionService( return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed val permissionGids = permission.getGidsForUser(userId) if (permissionGids.isEmpty()) { return@forEachIndexed @@ -662,9 +671,7 @@ class PermissionService( deviceId: Int, userId: Int ) { - setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = true - ) + setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true) } override fun revokeRuntimePermission( @@ -675,7 +682,12 @@ class PermissionService( reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason + packageName, + userId, + permissionName, + deviceId, + isGranted = false, + revokeReason = reason ) } @@ -684,8 +696,12 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, - isGranted = false, skipKillUid = true + packageName, + userId, + Manifest.permission.POST_NOTIFICATIONS, + Context.DEVICE_ID_DEFAULT, + isGranted = false, + skipKillUid = true ) } @@ -704,19 +720,24 @@ class PermissionService( ) { val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission" val callingUid = Binder.getCallingUid() - val isDebugEnabled = if (isGranted) { - PermissionManager.DEBUG_TRACE_GRANTS - } else { - PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - } - if (isDebugEnabled && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { + val isDebugEnabled = + if (isGranted) { + PermissionManager.DEBUG_TRACE_GRANTS + } else { + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + } + if ( + isDebugEnabled && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "$methodName(packageName = $packageName," + + LOG_TAG, + "$methodName(packageName = $packageName," + " permissionName = $permissionName" + (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + - ", userId = $userId," + " callingUid = $callingUidName ($callingUid))", + ", userId = $userId," + + " callingUid = $callingUidName ($callingUid))", RuntimeException() ) } @@ -727,23 +748,31 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, methodName + userId, + enforceFullPermission = true, + enforceShellRestriction = true, + methodName ) - val enforcedPermissionName = if (isGranted) { - Manifest.permission.GRANT_RUNTIME_PERMISSIONS - } else { - Manifest.permission.REVOKE_RUNTIME_PERMISSIONS - } + val enforcedPermissionName = + if (isGranted) { + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + } else { + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + } context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName) val packageState: PackageState? - val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ).first() + val permissionControllerPackageName = + packageManagerInternal + .getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + .first() val permissionControllerPackageState: PackageState? packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> - packageState = snapshot.filtered(callingUid, userId) - .use { it.getPackageState(packageName) } + packageState = + snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) } permissionControllerPackageState = snapshot.getPackageState(permissionControllerPackageName) } @@ -756,11 +785,13 @@ class PermissionService( return } - val canManageRolePermission = isRootOrSystemUid(callingUid) || - UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId - val overridePolicyFixed = context.checkCallingOrSelfPermission( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY - ) == PackageManager.PERMISSION_GRANTED + val canManageRolePermission = + isRootOrSystemUid(callingUid) || + UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId + val overridePolicyFixed = + context.checkCallingOrSelfPermission( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + ) == PackageManager.PERMISSION_GRANTED service.mutateState { with(onPermissionFlagsChangedListener) { @@ -773,8 +804,15 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, - overridePolicyFixed, reportError = true, methodName + packageState, + userId, + permissionName, + deviceId, + isGranted, + canManageRolePermission, + overridePolicyFixed, + reportError = true, + methodName ) } } @@ -791,8 +829,9 @@ class PermissionService( PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {} else -> { Slog.w( - LOG_TAG, "setRequestedPermissionStates: Unknown permission state" + - " $permissionState for permission $permissionName" + LOG_TAG, + "setRequestedPermissionStates: Unknown permission state" + + " $permissionState for permission $permissionName" ) return@forEachIndexed } @@ -800,35 +839,50 @@ class PermissionService( if (permissionName !in packageState.androidPackage!!.requestedPermissions) { return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed when { permission.isDevelopment || permission.isRuntime -> { - if (permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { + if ( + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + ) { setRuntimePermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, - isGranted = true, canManageRolePermission = false, - overridePolicyFixed = false, reportError = false, + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + isGranted = true, + canManageRolePermission = false, + overridePolicyFixed = false, + reportError = false, "setRequestedPermissionStates" ) updatePermissionFlags( - packageState.appId, userId, permissionName, + packageState.appId, + userId, + permissionName, Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "setRequestedPermissionStates", + isPermissionRequested = true, + "setRequestedPermissionStates", packageState.packageName ) } } - permission.isAppOp && permissionName in + permission.isAppOp && + permissionName in PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS -> setAppOpPermissionGranted( - packageState, userId, permissionName, permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + packageState, + userId, + permissionName, + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED ) else -> {} } @@ -836,9 +890,7 @@ class PermissionService( } } - /** - * Set whether a runtime permission is granted, without any validation on caller. - */ + /** Set whether a runtime permission is granted, without any validation on caller. */ private fun MutateStateScope.setRuntimePermissionGranted( packageState: PackageState, userId: Int, @@ -876,8 +928,11 @@ class PermissionService( // their permissions as always granted return } - if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp && - !permission.isInstant) { + if ( + isGranted && + packageState.getUserStateOrDefault(userId).isInstantApp && + !permission.isInstant + ) { if (reportError) { throw SecurityException( "Cannot grant non-instant permission $permissionName to package" + @@ -913,7 +968,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change system fixed permission $permissionName" + " for package $packageName" ) } @@ -923,7 +979,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change policy fixed permission $permissionName" + " for package $packageName" ) } @@ -933,7 +990,8 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant hard-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -942,14 +1000,19 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission( - context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, UserHandle.of(userId), permissionName - ) + val softRestrictedPermissionPolicy = + SoftRestrictedPermissionPolicy.forPermission( + context, + AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), + androidPackage, + UserHandle.of(userId), + permissionName + ) if (!softRestrictedPermissionPolicy.mayGrantPermission()) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -965,15 +1028,17 @@ class PermissionService( setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { - val action = if (isGranted) { - MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED - } else { - MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED - } - val log = LogMaker(action).apply { - setPackageName(packageName) - addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) - } + val action = + if (isGranted) { + MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED + } else { + MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED + } + val log = + LogMaker(action).apply { + setPackageName(packageName) + addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) + } metricsLogger.write(log) } } @@ -984,8 +1049,8 @@ class PermissionService( permissionName: String, isGranted: Boolean ) { - val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as - AppIdAppOpPolicy + val appOpPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy val appOpName = AppOpsManager.permissionToOp(permissionName)!! val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } @@ -1003,17 +1068,20 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "getPermissionFlags" ) enforceCallingOrSelfAnyPermission( - "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "getPermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, Manifest.permission.GET_RUNTIME_PERMISSIONS ) - val packageState = packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") return 0 @@ -1045,12 +1113,17 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "isPermissionRevokedByPolicy" ) - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return false service.getState { if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { @@ -1069,12 +1142,13 @@ class PermissionService( // TODO(b/173235285): Some caller may pass USER_ALL as userId. // Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } + ?: return false - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(packageState.appId, userId) } - } ?: return false + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } } + ?: return false return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) } } @@ -1090,13 +1164,18 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "shouldShowRequestPermissionRationale" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return false val appId = packageState.appId if (UserHandle.getAppId(callingUid) != appId) { return false @@ -1115,17 +1194,24 @@ class PermissionService( } if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) { - val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity { - try { - platformCompat.isChangeEnabledByPackageName( - BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId - ) - } catch (e: RemoteException) { - Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + - " compatibility change is enabled", e) - false + val isBackgroundRationaleChangeEnabled = + Binder::class.withClearedCallingIdentity { + try { + platformCompat.isChangeEnabledByPackageName( + BACKGROUND_RATIONALE_CHANGE_ID, + packageName, + userId + ) + } catch (e: RemoteException) { + Slog.e( + LOG_TAG, + "shouldShowRequestPermissionRationale: Unable to check if" + + " compatibility change is enabled", + e + ) + false + } } - } if (isBackgroundRationaleChangeEnabled) { return true } @@ -1144,20 +1230,30 @@ class PermissionService( userId: Int ) { val callingUid = Binder.getCallingUid() - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + if ( + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlags(packageName = $packageName," + + LOG_TAG, + "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1167,11 +1263,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlags" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1208,8 +1307,10 @@ class PermissionService( // Different from the old implementation, which returns when package doesn't exist but // throws when package exists but isn't visible, we now return in both cases to avoid // leaking the package existence. - if (androidPackage == null || - packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) { + if ( + androidPackage == null || + packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false) + ) { Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") return } @@ -1219,26 +1320,35 @@ class PermissionService( // permissions. val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) { - // Fast path, the current package has requested the permission. - true - } else { - // Slow path, go through all shared user packages. - val sharedUserPackageNames = - packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) - sharedUserPackageNames.any { sharedUserPackageName -> - val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) - sharedUserPackage != null && - permissionName in sharedUserPackage.requestedPermissions + val isPermissionRequested = + if (permissionName in androidPackage.requestedPermissions) { + // Fast path, the current package has requested the permission. + true + } else { + // Slow path, go through all shared user packages. + val sharedUserPackageNames = + packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) + sharedUserPackageNames.any { sharedUserPackageName -> + val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) + sharedUserPackage != null && + permissionName in sharedUserPackage.requestedPermissions + } } - } val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, - reportErrorForUnknownPermission = true, isPermissionRequested, - "updatePermissionFlags", packageName + appId, + userId, + permissionName, + deviceId, + flagMask, + flagValues, + canUpdateSystemFlags, + reportErrorForUnknownPermission = true, + isPermissionRequested, + "updatePermissionFlags", + packageName ) } } @@ -1246,17 +1356,25 @@ class PermissionService( override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) { val callingUid = Binder.getCallingUid() if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + + LOG_TAG, + "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1266,11 +1384,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlagsForAllApps" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlagsForAllApps", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1278,26 +1399,30 @@ class PermissionService( // flag, we now properly sanitize all flags as in updatePermissionFlags(). val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val packageStates = packageManagerLocal.withUnfilteredSnapshot() - .use { it.packageStates } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } service.mutateState { packageStates.forEach { (packageName, packageState) -> val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, - flagMask, flagValues, canUpdateSystemFlags, + packageState.appId, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + flagMask, + flagValues, + canUpdateSystemFlags, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName + isPermissionRequested = true, + "updatePermissionFlagsForAllApps", + packageName ) } } } } - /** - * Update flags for a permission, without any validation on caller. - */ + /** Update flags for a permission, without any validation on caller. */ private fun MutateStateScope.updatePermissionFlags( appId: Int, userId: Int, @@ -1311,20 +1436,19 @@ class PermissionService( methodName: String, packageName: String ) { - @Suppress("NAME_SHADOWING") - var flagMask = flagMask - @Suppress("NAME_SHADOWING") - var flagValues = flagValues + @Suppress("NAME_SHADOWING") var flagMask = flagMask + @Suppress("NAME_SHADOWING") var flagValues = flagValues // Only the system can change these flags and nothing else. if (!canUpdateSystemFlags) { // Different from the old implementation, which allowed non-system UIDs to remove (but // not add) permission restriction flags, we now consistently ignore them altogether. - val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or - PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or - PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or - PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION + val ignoredMask = + PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or + PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or + PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or + PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION flagMask = flagMask andInv ignoredMask flagValues = flagValues andInv ignoredMask } @@ -1340,7 +1464,8 @@ class PermissionService( val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( - LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + + LOG_TAG, + "$methodName: Permission $permissionName isn't requested by package" + " $packageName" ) return @@ -1365,21 +1490,29 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = false, enforceShellRestriction = false, + userId, + enforceFullPermission = false, + enforceShellRestriction = false, "getAllowlistedRestrictedPermissions" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return null + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return null val androidPackage = packageState.androidPackage ?: return null - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED - if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && - !isCallerPrivileged) { + if ( + allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && + !isCallerPrivileged + ) { throw SecurityException( "Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS @@ -1389,8 +1522,12 @@ class PermissionService( val isCallerInstallerOnRecord = packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid) - if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + if ( + allowlistedFlags.hasAnyBit( + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + ) + ) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { throw SecurityException( "Querying upgrade or installer allowlist requires being installer on record" + @@ -1400,7 +1537,9 @@ class PermissionService( } return getAllowlistedRestrictedPermissionsUnchecked( - packageState.appId, allowlistedFlags, userId + packageState.appId, + allowlistedFlags, + userId ) } @@ -1414,8 +1553,11 @@ class PermissionService( with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " get the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " get the flags for default device." + ) return with(policy) { getPermissionFlags(appId, userId, permissionName) } } val virtualDeviceManagerInternal = virtualDeviceManagerInternal @@ -1423,8 +1565,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return 0 } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { getPermissionFlags(appId, persistentDeviceId, userId, permissionName) @@ -1444,13 +1585,14 @@ class PermissionService( flags: Int ): Boolean { return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { - setPermissionFlags(appId, userId, permissionName, flags) - } + with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " set the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " set the flags for default device." + ) return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -1459,8 +1601,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return false } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) @@ -1473,17 +1614,17 @@ class PermissionService( } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun getAllowlistedRestrictedPermissionsUnchecked( appId: Int, allowlistedFlags: Int, userId: Int ): ArrayList<String>? { - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(appId, userId) } - } ?: return null + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(appId, userId) } } + ?: return null var queryFlags = 0 if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { @@ -1512,14 +1653,18 @@ class PermissionService( return false } - val permissionNames = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: ArrayList(1) + val permissionNames = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: ArrayList(1) if (permissionName !in permissionNames) { permissionNames += permissionName return setAllowlistedRestrictedPermissions( - packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true + packageName, + permissionNames, + allowlistedFlags, + userId, + isAddingPermission = true ) } return false @@ -1531,14 +1676,22 @@ class PermissionService( permissionNames: List<String>, userId: Int ) { - val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId - )?.let { - ArraySet(permissionNames).apply { this += it }.toList() - } ?: permissionNames + val newPermissionNames = + getAllowlistedRestrictedPermissionsUnchecked( + appId, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) + ?.let { ArraySet(permissionNames).apply { this += it }.toList() } + ?: permissionNames - setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId) + setAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + appId, + newPermissionNames, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) } override fun removeAllowlistedRestrictedPermission( @@ -1552,13 +1705,17 @@ class PermissionService( return false } - val permissions = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: return false + val permissions = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: return false if (permissions.remove(permissionName)) { return setAllowlistedRestrictedPermissions( - packageName, permissions, allowlistedFlags, userId, isAddingPermission = false + packageName, + permissions, + allowlistedFlags, + userId, + isAddingPermission = false ) } @@ -1572,16 +1729,22 @@ class PermissionService( return false } - if (packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(permission.packageName) } == null) { + if ( + packageManagerLocal.withFilteredSnapshot().use { + it.getPackageState(permission.packageName) + } == null + ) { return false } val isImmutablyRestrictedPermission = permission.isHardOrSoftRestricted && permission.isImmutablyRestricted - if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) != PackageManager.PERMISSION_GRANTED) { + if ( + isImmutablyRestrictedPermission && + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) != PackageManager.PERMISSION_GRANTED + ) { throw SecurityException( "Cannot modify allowlist of an immutably restricted permission: ${permission.name}" ) @@ -1599,13 +1762,16 @@ class PermissionService( ): Boolean { Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1) - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { snapshot -> snapshot.packageStates[packageName] ?: return false } + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot -> + snapshot.packageStates[packageName] ?: return false + } val androidPackage = packageState.androidPackage ?: return false val isCallerInstallerOnRecord = @@ -1627,15 +1793,19 @@ class PermissionService( } setAllowlistedRestrictedPermissionsUnchecked( - androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId + androidPackage, + packageState.appId, + permissionNames, + allowlistedFlags, + userId ) return true } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun setAllowlistedRestrictedPermissionsUnchecked( androidPackage: AndroidPackage, @@ -1712,22 +1882,24 @@ class PermissionService( } } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = mask or PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + mask = + mask or + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SOFT_RESTRICTED - updatePermissionFlags( - appId, userId, requestedPermission, mask, newFlags - ) + updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) } } } @@ -1735,12 +1907,8 @@ class PermissionService( override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) { service.mutateState { - with(policy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } - with(devicePolicy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } + with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) } } } @@ -1748,12 +1916,8 @@ class PermissionService( packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> service.mutateState { snapshot.packageStates.forEach { (_, packageState) -> - with(policy) { - resetRuntimePermissions(packageState.packageName, userId) - } - with(devicePolicy) { - resetRuntimePermissions(packageState.packageName, userId) - } + with(policy) { resetRuntimePermissions(packageState.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) } } } } @@ -1761,7 +1925,8 @@ class PermissionService( override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) { context.enforceCallingOrSelfPermission( - Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener" + Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, + "addOnPermissionsChangeListener" ) onPermissionsChangeListeners.addListener(listener) @@ -1786,9 +1951,7 @@ class PermissionService( requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } + val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null || !permission.isAppOp) { packageNames.toTypedArray() } @@ -1814,8 +1977,8 @@ class PermissionService( androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName -> val permission = permissions[permissionName] ?: return@requestedPermissions if (permission.isAppOp) { - val packageNames = appOpPermissionPackageNames - .getOrPut(permissionName) { ArraySet() } + val packageNames = + appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() } packageNames += androidPackage.packageName } } @@ -1828,14 +1991,18 @@ class PermissionService( Preconditions.checkArgumentNonnegative(userId, "userId cannot be null") val backup = CompletableFuture<ByteArray>() permissionControllerManager.getRuntimePermissionBackup( - UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete + UserHandle.of(userId), + PermissionThread.getExecutor(), + backup::complete ) return try { backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) } catch (e: Exception) { when (e) { - is TimeoutException, is InterruptedException, is ExecutionException -> { + is TimeoutException, + is InterruptedException, + is ExecutionException -> { Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e) null } @@ -1852,7 +2019,8 @@ class PermissionService( isDelayedPermissionBackupFinished -= userId } permissionControllerManager.stageAndApplyRuntimePermissionsBackup( - backup, UserHandle.of(userId) + backup, + UserHandle.of(userId) ) } @@ -1866,7 +2034,9 @@ class PermissionService( } } permissionControllerManager.applyStagedRuntimePermissionBackup( - packageName, UserHandle.of(userId), PermissionThread.getExecutor() + packageName, + UserHandle.of(userId), + PermissionThread.getExecutor() ) { hasMoreBackup -> if (hasMoreBackup) { return@applyStagedRuntimePermissionBackup @@ -1913,21 +2083,15 @@ class PermissionService( ): IndexedMap<Int, MutableIndexedSet<String>> { val appIds = MutableIndexedSet<Int>() - val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { - it.packageStates - } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } state.userStates.forEachIndexed { _, _, userState -> - userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ -> + userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageVersions appIds.add(appId) } - userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ -> + userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes appIds.add(appId) } @@ -1935,7 +2099,8 @@ class PermissionService( val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>() packageStates.forEach { (_, packageState) -> - appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() } + appIdPackageNames + .getOrPut(packageState.appId) { MutableIndexedSet() } .add(packageState.packageName) } // add non-package app IDs which might not be reported by package manager. @@ -1966,10 +2131,7 @@ class PermissionService( println("Permission groups:") withIndent { state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup -> - println( - "${permissionGroup.name}: " + - "packageName=${permissionGroup.packageName}" - ) + println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}") } } @@ -1998,7 +2160,9 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, + permissionName, + flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -2008,7 +2172,9 @@ class PermissionService( } userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { - _, deviceId, devicePermissionFlags -> + _, + deviceId, + devicePermissionFlags -> println("Permissions (Device $deviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> @@ -2023,7 +2189,8 @@ class PermissionService( println("App ops:") withIndent { - userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> + userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode + -> println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}") } } @@ -2035,7 +2202,9 @@ class PermissionService( println("App ops:") withIndent { userState.packageAppOpModes[packageName]?.forEachIndexed { - _, appOpName, appOpMode -> + _, + appOpName, + appOpMode -> val modeName = AppOpsManager.modeToName(appOpMode) println("$appOpName: mode=$modeName") } @@ -2054,24 +2223,30 @@ class PermissionService( } override fun getPermissionTEMP(permissionName: String): LegacyPermission2? { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null return LegacyPermission2( - permission.permissionInfo, permission.type, permission.isReconciled, permission.appId, - permission.gids, permission.areGidsPerUser + permission.permissionInfo, + permission.type, + permission.isReconciled, + permission.appId, + permission.gids, + permission.areGidsPerUser ) } override fun getLegacyPermissions(): List<LegacyPermission> = - service.getState { - with(policy) { getPermissions() } - }.mapIndexedTo(ArrayList()) { _, _, permission -> - LegacyPermission( - permission.permissionInfo, permission.type, permission.appId, permission.gids - ) - } + service + .getState { with(policy) { getPermissions() } } + .mapIndexedTo(ArrayList()) { _, _, permission -> + LegacyPermission( + permission.permissionInfo, + permission.type, + permission.appId, + permission.gids + ) + } override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) { // Package settings has been read when this method is called. @@ -2092,9 +2267,7 @@ class PermissionService( ): List<LegacyPermission> = permissions.mapIndexedTo(ArrayList()) { _, _, permission -> // We don't need to provide UID and GIDs, which are only retrieved when dumping. - LegacyPermission( - permission.permissionInfo, permission.type, 0, EmptyArray.INT - ) + LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT) } override fun getLegacyPermissionState(appId: Int): LegacyPermissionState { @@ -2103,17 +2276,18 @@ class PermissionService( service.getState { val permissions = with(policy) { getPermissions() } userIds.forEachIndexed { _, userId -> - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return@forEachIndexed + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags -> val permission = permissions[permissionName] ?: return@permissionFlags - val legacyPermissionState = LegacyPermissionState.PermissionState( - permissionName, - permission.isRuntime, - PermissionFlags.isPermissionGranted(flags), - PermissionFlags.toApiFlags(flags) - ) + val legacyPermissionState = + LegacyPermissionState.PermissionState( + permissionName, + permission.isRuntime, + PermissionFlags.isPermissionGranted(flags), + PermissionFlags.toApiFlags(flags) + ) legacyState.putPermissionState(legacyPermissionState, userId) } } @@ -2137,16 +2311,13 @@ class PermissionService( override fun onSystemReady() { service.onSystemReady() virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) - permissionControllerManager = PermissionControllerManager( - context, PermissionThread.getHandler() - ) + LocalServices.getService(VirtualDeviceManagerInternal::class.java) + permissionControllerManager = + PermissionControllerManager(context, PermissionThread.getHandler()) } override fun onUserCreated(userId: Int) { - withCorkedPackageInfoCache { - service.onUserAdded(userId) - } + withCorkedPackageInfoCache { service.onUserAdded(userId) } } override fun onUserRemoved(userId: Int) { @@ -2176,9 +2347,8 @@ class PermissionService( // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the // packages to be iterated in onStorageVolumeAdded() in the same order so that the // ownership of permissions is consistent. - storageVolumePackageNames.getOrPut(packageState.volumeUuid) { - mutableListOf() - } += packageState.packageName + storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } += + packageState.packageName if (packageState.volumeUuid !in mountedStorageVolumes) { // Wait for the storage volume to be mounted and batch the state mutation there. return @@ -2218,23 +2388,26 @@ class PermissionService( return } } - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } @Suppress("NAME_SHADOWING") - userIds.forEach { userId -> - service.onPackageInstalled(androidPackage.packageName, userId) - } + userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) } @Suppress("NAME_SHADOWING") userIds.forEach { userId -> // TODO: Remove when this callback receives packageState directly. val packageState = packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!! - addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId, - params.allowlistedRestrictedPermissions, userId) + addAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + packageState.appId, + params.allowlistedRestrictedPermissions, + userId + ) setRequestedPermissionStates(packageState, userId, params.permissionStates) } } @@ -2247,11 +2420,12 @@ class PermissionService( sharedUserPkgs: List<AndroidPackage>, userId: Int ) { - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } userIds.forEach { service.onPackageUninstalled(packageName, appId, it) } val packageState = packageManagerInternal.packageStates[packageName] if (packageState == null) { @@ -2268,31 +2442,26 @@ class PermissionService( } } - /** - * Check whether a UID is root or system UID. - */ + /** Check whether a UID is root or system UID. */ private fun isRootOrSystemUid(uid: Int) = when (UserHandle.getAppId(uid)) { - Process.ROOT_UID, Process.SYSTEM_UID -> true + Process.ROOT_UID, + Process.SYSTEM_UID -> true else -> false } - /** - * Check whether a UID is shell UID. - */ + /** Check whether a UID is shell UID. */ private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID - /** - * Check whether a UID is root, system or shell UID. - */ + /** Check whether a UID is root, system or shell UID. */ private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid) /** * This method should typically only be used when granting or revoking permissions, since the * app may immediately restart after this call. * - * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against - * the app being relaunched. + * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the + * app being relaunched. */ private fun killUid(uid: Int, reason: String) { val activityManager = ActivityManager.getService() @@ -2309,9 +2478,7 @@ class PermissionService( } } - /** - * @see PackageManagerLocal.withFilteredSnapshot - */ + /** @see PackageManagerLocal.withFilteredSnapshot */ private fun PackageManagerLocal.withFilteredSnapshot( callingUid: Int, userId: Int @@ -2329,35 +2496,27 @@ class PermissionService( packageName: String ): PackageState? = packageStates[packageName] - /** - * Check whether a UID belongs to an instant app. - */ + /** Check whether a UID belongs to an instant app. */ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean = // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the // API for getting the SharedUserApi object for an app ID yet, so for now we just keep // calling the old API. packageManagerInternal.getInstantAppPackageName(uid) != null - /** - * Check whether a package is visible to a UID within the same user as the UID. - */ + /** Check whether a package is visible to a UID within the same user as the UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, uid: Int ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid) - /** - * Check whether a package in a particular user is visible to a UID. - */ + /** Check whether a package in a particular user is visible to a UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, userId: Int, uid: Int ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null } - /** - * @see PackageManagerLocal.UnfilteredSnapshot.filtered - */ + /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */ private fun PackageManagerLocal.UnfilteredSnapshot.filtered( callingUid: Int, userId: Int @@ -2380,13 +2539,16 @@ class PermissionService( val callingUid = Binder.getCallingUid() val callingUserId = UserHandle.getUserId(callingUid) if (userId != callingUserId) { - val permissionName = if (enforceFullPermission) { - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } else { - Manifest.permission.INTERACT_ACROSS_USERS - } - if (context.checkCallingOrSelfPermission(permissionName) != - PackageManager.PERMISSION_GRANTED) { + val permissionName = + if (enforceFullPermission) { + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } else { + Manifest.permission.INTERACT_ACROSS_USERS + } + if ( + context.checkCallingOrSelfPermission(permissionName) != + PackageManager.PERMISSION_GRANTED + ) { val exceptionMessage = buildString { if (message != null) { append(message) @@ -2403,9 +2565,11 @@ class PermissionService( } } if (enforceShellRestriction && isShellUid(callingUid)) { - val isShellRestricted = userManagerInternal.hasUserRestriction( - UserManager.DISALLOW_DEBUGGING_FEATURES, userId - ) + val isShellRestricted = + userManagerInternal.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userId + ) if (isShellRestricted) { val exceptionMessage = buildString { if (message != null) { @@ -2430,10 +2594,11 @@ class PermissionService( message: String?, vararg permissionNames: String ) { - val hasAnyPermission = permissionNames.any { permissionName -> - context.checkCallingOrSelfPermission(permissionName) == - PackageManager.PERMISSION_GRANTED - } + val hasAnyPermission = + permissionNames.any { permissionName -> + context.checkCallingOrSelfPermission(permissionName) == + PackageManager.PERMISSION_GRANTED + } if (!hasAnyPermission) { val exceptionMessage = buildString { if (message != null) { @@ -2449,9 +2614,7 @@ class PermissionService( } } - /** - * Callback invoked when interesting actions have been taken on a permission. - */ + /** Callback invoked when interesting actions have been taken on a permission. */ private inner class OnPermissionFlagsChangedListener : AppIdPermissionPolicy.OnPermissionFlagsChangedListener() { private var isPermissionFlagsChanged = false @@ -2482,9 +2645,8 @@ class PermissionService( isPermissionFlagsChanged = true val uid = UserHandle.getUid(userId, appId) - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags) val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags) @@ -2518,16 +2680,20 @@ class PermissionService( runtimePermissionChangedUids.clear() if (!isKillRuntimePermissionRevokedUidsSkipped) { - val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { - killRuntimePermissionRevokedUidsReasons.joinToString(", ") - } else { - PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED - } + val reason = + if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { + killRuntimePermissionRevokedUidsReasons.joinToString(", ") + } else { + PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED + } runtimePermissionRevokedUids.forEachIndexed { - _, uid, areOnlyNotificationsPermissionsRevoked -> + _, + uid, + areOnlyNotificationsPermissionsRevoked -> handler.post { - if (areOnlyNotificationsPermissionsRevoked && - isAppBackupAndRestoreRunning(uid) + if ( + areOnlyNotificationsPermissionsRevoked && + isAppBackupAndRestoreRunning(uid) ) { return@post } @@ -2547,19 +2713,27 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != - PackageManager.PERMISSION_GRANTED) { + if ( + checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != + PackageManager.PERMISSION_GRANTED + ) { return false } return try { val contentResolver = context.contentResolver val userId = UserHandle.getUserId(uid) - val isInSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId - ) == 0 - val isInDeferredSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId - ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED + val isInSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_COMPLETE, + userId + ) == 0 + val isInDeferredSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, + userId + ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED isInSetup || isInDeferredSetup } catch (e: Settings.SettingNotFoundException) { Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e") @@ -2620,31 +2794,34 @@ class PermissionService( @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L - private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply { - this[Manifest.permission.ACCESS_COARSE_LOCATION] = - Manifest.permission.ACCESS_FINE_LOCATION - this[Manifest.permission.INTERACT_ACROSS_USERS] = - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } + private val FULLER_PERMISSIONS = + ArrayMap<String, String>().apply { + this[Manifest.permission.ACCESS_COARSE_LOCATION] = + Manifest.permission.ACCESS_FINE_LOCATION + this[Manifest.permission.INTERACT_ACROSS_USERS] = + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } - private val NOTIFICATIONS_PERMISSIONS = arraySetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS) - private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or - PermissionFlags.IMPLICIT - private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or - PermissionFlags.USER_FIXED + private const val REVIEW_REQUIRED_FLAGS = + PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + private const val UNREQUESTABLE_MASK = + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SYSTEM_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.USER_FIXED private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60) - /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + /** + * Cap the size of permission trees that 3rd party apps can define; in characters of text + */ private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768 private const val PERMISSION_ALLOWLIST_MASK = PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER } } diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index bd829351941c..6b20ef1d5519 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -25,9 +25,7 @@ import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException -/** - * Read from an [AtomicFile], fallback to reserve file to read the data. - */ +/** Read from an [AtomicFile], fallback to reserve file to read the data. */ @Throws(Exception::class) inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { try { @@ -46,9 +44,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { } } -/** - * Write to actual file and reserve file. - */ +/** Write to actual file and reserve file. */ @Throws(IOException::class) inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") @@ -66,9 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } } -/** - * Write to an [AtomicFile] and close everything safely when done. - */ +/** Write to an [AtomicFile] and close everything safely when done. */ @Throws(IOException::class) // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline. inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt index 1d27aef39a2c..6ab73c76007e 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt @@ -22,9 +22,7 @@ import java.io.InputStream import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -/** - * Parse content from [InputStream] with [BinaryXmlPullParser]. - */ +/** Parse content from [InputStream] with [BinaryXmlPullParser]. */ @Throws(IOException::class, XmlPullParserException::class) inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { BinaryXmlPullParser().apply { @@ -35,6 +33,7 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { /** * Iterate through child tags of the current tag. + * * <p> * Attributes for the current tag needs to be accessed before this method is called because this * method will advance the parser past the start tag of the current tag. The code inspecting each @@ -50,7 +49,8 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) { when (val eventType = eventType) { // Document start or start tag of the parent tag. - XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd() + XmlPullParser.START_DOCUMENT, + XmlPullParser.START_TAG -> nextTagOrEnd() else -> throw XmlPullParserException("Unexpected event type $eventType") } while (true) { @@ -90,7 +90,8 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) nextTagOrEnd() } // End tag of the parent tag, or document end. - XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> break else -> throw XmlPullParserException("Unexpected event type $eventType") } } @@ -107,193 +108,146 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) inline fun BinaryXmlPullParser.nextTagOrEnd(): Int { while (true) { when (val eventType = next()) { - XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> - return eventType + XmlPullParser.START_TAG, + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> return eventType else -> continue } } } -/** - * @see BinaryXmlPullParser.getName - */ +/** @see BinaryXmlPullParser.getName */ inline val BinaryXmlPullParser.tagName: String get() = name -/** - * Check whether an attribute exists for the current tag. - */ +/** Check whether an attribute exists for the current tag. */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1 -/** - * @see BinaryXmlPullParser.getAttributeIndex - */ +/** @see BinaryXmlPullParser.getAttributeIndex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIndexOrThrow - */ +/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int = getAttributeIndexOrThrow(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? = getAttributeValue(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String = getAttributeValue(getAttributeIndexOrThrow(name)) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? = getAttributeBytesHex(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray = getAttributeBytesHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? = getAttributeBytesBase64(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray = getAttributeBytesBase64(null, name) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int = getAttributeInt(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int = getAttributeInt(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int = getAttributeIntHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int = getAttributeIntHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long = getAttributeLong(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long = getAttributeLong(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault( name: String, defaultValue: Long ): Long = getAttributeLongHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long = getAttributeLongHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeFloatOrDefault( name: String, defaultValue: Float ): Float = getAttributeFloat(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float = getAttributeFloat(null, name) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault( name: String, defaultValue: Double ): Double = getAttributeDouble(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double = getAttributeDouble(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault( name: String, defaultValue: Boolean ): Boolean = getAttributeBoolean(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt index c8cd5866adbc..6500a7d2e0a2 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt @@ -20,9 +20,7 @@ import com.android.modules.utils.BinaryXmlSerializer import java.io.IOException import java.io.OutputStream -/** - * Serialize content into [OutputStream] with [BinaryXmlSerializer]. - */ +/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */ @Throws(IOException::class) inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) { BinaryXmlSerializer().apply { @@ -57,54 +55,42 @@ inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() - endTag(null, name) } -/** - * @see BinaryXmlSerializer.attribute - */ +/** @see BinaryXmlSerializer.attribute */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attribute(name: String, value: String) { attribute(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInterned - */ +/** @see BinaryXmlSerializer.attributeInterned */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) { attributeInterned(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesHex - */ +/** @see BinaryXmlSerializer.attributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) { attributeBytesHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesBase64 - */ +/** @see BinaryXmlSerializer.attributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) { attributeBytesBase64(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) { attributeInt(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntWithDefault( @@ -117,18 +103,14 @@ inline fun BinaryXmlSerializer.attributeIntWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) { attributeIntHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHexWithDefault( @@ -141,18 +123,14 @@ inline fun BinaryXmlSerializer.attributeIntHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) { attributeLong(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongWithDefault( @@ -165,18 +143,14 @@ inline fun BinaryXmlSerializer.attributeLongWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) { attributeLongHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHexWithDefault( @@ -189,18 +163,14 @@ inline fun BinaryXmlSerializer.attributeLongHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) { attributeFloat(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloatWithDefault( @@ -213,18 +183,14 @@ inline fun BinaryXmlSerializer.attributeFloatWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) { attributeDouble(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDoubleWithDefault( @@ -237,18 +203,14 @@ inline fun BinaryXmlSerializer.attributeDoubleWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) { attributeBoolean(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBooleanWithDefault( diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt index a61489cd21c5..3ef284b05961 100644 --- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt @@ -22,13 +22,13 @@ import com.android.server.pm.permission.PermissionMigrationHelper object PackageVersionMigration { /** - * Maps existing permission and app-op version to a unified version during OTA upgrade. The - * new unified version is used in determining the upgrade steps for a package (for both - * permission and app-ops). + * Maps existing permission and app-op version to a unified version during OTA upgrade. The new + * unified version is used in determining the upgrade steps for a package (for both permission + * and app-ops). * * @return unified permission/app-op version * @throws IllegalStateException if the method is called when there is nothing to migrate i.e. - * permission and app-op file does not exist. + * permission and app-op file does not exist. */ internal fun getVersion(userId: Int): Int { val permissionMigrationHelper = diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt index e6b4e3e67b61..3d835e8c3cf3 100644 --- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt +++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt @@ -23,15 +23,11 @@ import java.io.File object PermissionApex { private const val MODULE_NAME = "com.android.permission" - /** - * @see ApexEnvironment.getDeviceProtectedDataDir - */ + /** @see ApexEnvironment.getDeviceProtectedDataDir */ val systemDataDirectory: File get() = apexEnvironment.deviceProtectedDataDir - /** - * @see ApexEnvironment.getDeviceProtectedDataDirForUser - */ + /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */ fun getUserDataDirectory(userId: Int): File = apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId)) diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 2889c749f679..cbedcaf97358 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -24,7 +24,9 @@ import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import static android.content.res.Resources.ID_NULL; + import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -41,6 +43,7 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.PropertyInvalidatedCache; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; @@ -871,12 +874,20 @@ public class PackageManagerSettingsTests { .setUid(packageSetting.getAppId()) .hideAsFinal()); - ArchiveState archiveState = new ArchiveState( - List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"), - Path.of("/monochromePath1")), - new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"), - Path.of("/monochromePath2"))), - "installerTitle"); + ArchiveState archiveState = + new ArchiveState( + List.of( + new ArchiveState.ArchiveActivityInfo( + "title1", + new ComponentName("pkg1", "class1"), + Path.of("/path1"), + Path.of("/monochromePath1")), + new ArchiveState.ArchiveActivityInfo( + "title2", + new ComponentName("pkg2", "class2"), + Path.of("/path2"), + Path.of("/monochromePath2"))), + "installerTitle"); packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState( archiveState); settings.mPackages.put(PACKAGE_NAME_1, packageSetting); @@ -1065,7 +1076,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a")); assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a")); assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi")); @@ -1103,7 +1117,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a")); assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a")); assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi")); @@ -1143,7 +1160,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); fail("Expected a PackageManagerException"); } catch (PackageManagerException expected) { } @@ -1179,7 +1199,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH)); assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM)); @@ -1224,7 +1247,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getAppId(), is(0)); assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH)); assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1269,7 +1295,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getAppId(), is(10064)); assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH)); assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1315,7 +1344,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getAppId(), is(10064)); assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH)); assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1358,7 +1390,10 @@ public class PackageManagerSettingsTests { null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, - UUID.randomUUID()); + UUID.randomUUID(), + false /*isPersistent*/, + 34 /*targetSdkVersion*/, + null /*restrictUpdateHash*/); assertThat(testPkgSetting01.getAppId(), is(0)); assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH)); assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 58ae7406580e..87a297b0e86f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.overlay.OverlayPaths; @@ -192,8 +193,8 @@ public class PackageUserStateTest { return new SuspendParams(dialogInfo, appExtras, launcherExtras); } - private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey, - String sValue, String dKey, double dValue) { + private static PersistableBundle createPersistableBundle( + String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) { final PersistableBundle result = new PersistableBundle(3); if (lKey != null) { result.putLong("com.unit_test." + lKey, lValue); @@ -320,6 +321,7 @@ public class PackageUserStateTest { assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]); } } + private static void assertLastPackageUsageSet( PackageStateUnserialized state, int reason, long value) throws Exception { for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) { @@ -330,6 +332,7 @@ public class PackageUserStateTest { } } } + @Test public void testPackageUseReasons() throws Exception { PackageSetting packageSetting = Mockito.mock(PackageSetting.class); @@ -377,6 +380,7 @@ public class PackageUserStateTest { assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build())); assertFalse(testState.setOverlayPaths(null)); } + @Test public void testSharedLibOverlayPaths() { final PackageUserStateImpl testState = new PackageUserStateImpl(); @@ -401,8 +405,12 @@ public class PackageUserStateTest { @Test public void archiveState() { PackageUserStateImpl packageUserState = new PackageUserStateImpl(); - ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo( - "appTitle", Path.of("/path1"), Path.of("/path2")); + ArchiveState.ArchiveActivityInfo archiveActivityInfo = + new ArchiveState.ArchiveActivityInfo( + "appTitle", + new ComponentName("pkg", "class"), + Path.of("/path1"), + Path.of("/path2")); ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo), "installerTitle"); packageUserState.setArchiveState(archiveState); diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index 0275c7df4648..6e4069fbe4bd 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -40,6 +40,7 @@ android_test { "services.core", "servicestests-utils", "testables", + "TestParameterInjector", ], defaults: [ diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index 55fde00a05db..e71ea263983a 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -28,6 +28,7 @@ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.MANAGE_USB" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 4b124caca7d6..8cc3408ad79b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -100,6 +100,14 @@ public class DeviceStateToLayoutMapTest { } @Test + public void testPowerThrottlingMapId() { + Layout configLayout = mDeviceStateToLayoutMap.get(5); + + assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId()); + assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId()); + } + + @Test public void testRearDisplayLayout() { Layout configLayout = mDeviceStateToLayoutMap.get(2); @@ -133,13 +141,15 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1", /* refreshRateZoneId= */ "zone1", - /* refreshRateThermalThrottlingMapId= */ "rr1"); + /* refreshRateThermalThrottlingMapId= */ "rr1", + /* powerThrottlingMapId= */ "power1"); testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1", mDisplayIdProducerMock, Layout.Display.POSITION_REAR, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2", /* refreshRateZoneId= */ "zone2", - /* refreshRateThermalThrottlingMapId= */ "rr2"); + /* refreshRateThermalThrottlingMapId= */ "rr2", + /* powerThrottlingMapId= */ "power2"); testLayout.postProcessLocked(); assertEquals(testLayout, configLayout); @@ -200,7 +210,8 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, DisplayAddress.fromPhysicalDisplayId(123L), /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null)); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null)); } @Test @@ -215,7 +226,8 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, DisplayAddress.fromPhysicalDisplayId(987L), /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null)); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null)); } @Test @@ -271,7 +283,8 @@ public class DeviceStateToLayoutMapTest { enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN, leadDisplayAddress, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null); } private void setupDeviceStateToLayoutMap() throws IOException { @@ -327,7 +340,6 @@ public class DeviceStateToLayoutMapTest { + "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n" + "</display>\n" + "</layout>\n" - + "<layout>\n" + "<state>3</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" " @@ -338,7 +350,6 @@ public class DeviceStateToLayoutMapTest { + "<address>678</address>\n" + "</display>\n" + "</layout>\n" - + "<layout>\n" + "<state>4</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" >\n" @@ -352,6 +363,20 @@ public class DeviceStateToLayoutMapTest { + "</display>\n" + "</layout>\n" + "<layout>\n" + + "<state>5</state> \n" + + "<display enabled=\"true\" defaultDisplay=\"true\">\n" + + "<address>345</address>\n" + + "<position>front</position>\n" + + "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n" + + "</display>\n" + + "<display enabled=\"true\">\n" + + "<address>678</address>\n" + + "<position>rear</position>\n" + + "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n" + + "</display>\n" + + "</layout>\n" + + + "<layout>\n" + "<state>99</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" " + "refreshRateZoneId=\"zone1\">\n" @@ -361,6 +386,7 @@ public class DeviceStateToLayoutMapTest { + "<refreshRateThermalThrottlingMapId>" + "rr1" + "</refreshRateThermalThrottlingMapId>" + + "<powerThrottlingMapId>power1</powerThrottlingMapId>\n" + "</display>\n" + "<display enabled=\"false\" displayGroup=\"group1\" " + "refreshRateZoneId=\"zone2\">\n" @@ -370,6 +396,7 @@ public class DeviceStateToLayoutMapTest { + "<refreshRateThermalThrottlingMapId>" + "rr2" + "</refreshRateThermalThrottlingMapId>" + + "<powerThrottlingMapId>power2</powerThrottlingMapId>\n" + "</display>\n" + "</layout>\n" + "</layouts>\n"; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 2396905aecbf..16d72e40fbb5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -114,17 +114,17 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; -import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Expect; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; -import com.google.common.truth.Expect; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -201,9 +201,12 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, - displayAdapterListener, flags, new LocalDisplayAdapter.Injector() { + displayAdapterListener, flags, + mMockedDisplayNotificationManager, + new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; @@ -248,13 +251,15 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter( syncRoot, context, handler, displayAdapterListener, flags, + mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { @@ -288,6 +293,7 @@ public class DisplayManagerServiceTest { private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); + @Mock DisplayNotificationManager mMockedDisplayNotificationManager; @Mock IMediaProjectionManager mMockProjectionService; @Mock IVirtualDeviceManager mIVirtualDeviceManager; @Mock InputManagerInternal mMockInputManagerInternal; @@ -305,7 +311,6 @@ public class DisplayManagerServiceTest { @Mock SensorManager mSensorManager; @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; @Mock PackageManagerInternal mMockPackageManagerInternal; - @Mock UserManagerInternal mMockUserManagerInternal; @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -329,8 +334,6 @@ public class DisplayManagerServiceTest { VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); - LocalServices.removeServiceForTest(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = Mockito.spy(mContext.getResources()); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 147e8f22aab6..32e28715cc74 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.content.Context; @@ -60,6 +61,7 @@ import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -113,6 +115,8 @@ public class LocalDisplayAdapterTest { @Mock private LogicalLight mMockedBacklight; @Mock + private DisplayNotificationManager mMockedDisplayNotificationManager; + @Mock private DisplayManagerFlags mFlags; private Handler mHandler; @@ -148,7 +152,7 @@ public class LocalDisplayAdapterTest { mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, - mListener, mFlags, mInjector); + mListener, mFlags, mMockedDisplayNotificationManager, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); @@ -696,7 +700,7 @@ public class LocalDisplayAdapterTest { // Turn off. Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0, - 0); + 0, null); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.changedDisplays.size()).isEqualTo(1); mListener.changedDisplays.clear(); @@ -1000,7 +1004,7 @@ public class LocalDisplayAdapterTest { // Turn on / initialize assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline()); Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, - 0); + 0, null); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); @@ -1009,7 +1013,7 @@ public class LocalDisplayAdapterTest { // HDR time! Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f, - 0); + 0, null); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // Display state didn't change, no listeners should have happened assertThat(mListener.changedDisplays.size()).isEqualTo(0); @@ -1040,7 +1044,7 @@ public class LocalDisplayAdapterTest { // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, - 0); + 0, null); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); @@ -1067,7 +1071,7 @@ public class LocalDisplayAdapterTest { // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, - 0); + 0, null); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); @@ -1092,7 +1096,7 @@ public class LocalDisplayAdapterTest { // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, - 0); + 0, null); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); @@ -1115,7 +1119,7 @@ public class LocalDisplayAdapterTest { // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, - 0); + 0, null); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); @@ -1142,9 +1146,9 @@ public class LocalDisplayAdapterTest { Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked( supportedState, 0, 0, mDisplayOffloadSession); changeStateRunnable.run(); - - verify(mDisplayOffloader).startOffload(); } + + verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload(); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 065dd1f1f743..8b13018fc14b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -720,13 +720,15 @@ public class LogicalDisplayMapperTest { mIdProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ "concurrent"); layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ "concurrent"); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); layout = new Layout(); @@ -927,7 +929,7 @@ public class LogicalDisplayMapperTest { /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null, mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */null); + /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1); @@ -986,7 +988,7 @@ public class LogicalDisplayMapperTest { layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer, Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null); } private void advanceTime(long timeMs) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index 22d26224c83f..c0e0df98213f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.testutils.TestHandler; import org.junit.Before; @@ -63,12 +64,13 @@ public class BrightnessClamperControllerTest { @Mock private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper; @Mock + private DisplayManagerFlags mFlags; + @Mock private BrightnessModifier mMockModifier; @Mock private DisplayManagerInternal.DisplayPowerRequest mMockRequest; @Mock private DeviceConfig.Properties mMockProperties; - private BrightnessClamperController mClamperController; private TestInjector mTestInjector; @@ -219,7 +221,7 @@ public class BrightnessClamperControllerTest { private BrightnessClamperController createBrightnessClamperController() { return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener, - mMockDisplayDeviceData, mMockContext); + mMockDisplayDeviceData, mMockContext, mFlags); } private class TestInjector extends BrightnessClamperController.Injector { @@ -247,7 +249,8 @@ public class BrightnessClamperControllerTest { List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers( Handler handler, BrightnessClamperController.ClamperChangeListener clamperChangeListener, - BrightnessClamperController.DisplayDeviceData data) { + BrightnessClamperController.DisplayDeviceData data, + DisplayManagerFlags flags) { mCapturedChangeListener = clamperChangeListener; return mClampers; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java new file mode 100644 index 000000000000..b3f33ad858fe --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.Temperature; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.testutils.FakeDeviceConfigInterface; +import com.android.server.testutils.TestHandler; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@RunWith(JUnitParamsRunner.class) +public class BrightnessPowerClamperTest { + private static final String TAG = "BrightnessPowerClamperTest"; + private static final float FLOAT_TOLERANCE = 0.001f; + + private static final String DISPLAY_ID = "displayId"; + @Mock + private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + private TestPmicMonitor mPmicMonitor; + private final FakeDeviceConfigInterface mFakeDeviceConfigInterface = + new FakeDeviceConfigInterface(); + private final TestHandler mTestHandler = new TestHandler(null); + private BrightnessPowerClamper mClamper; + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler, + mMockClamperChangeListener, new TestPowerData()); + mTestHandler.flush(); + } + + @Test + public void testTypeIsPower() { + assertEquals(BrightnessClamper.Type.POWER, mClamper.getType()); + } + + @Test + public void testNoThrottlingData() { + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingNoOngoingAnimation() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); + mTestHandler.flush(); + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + + mPmicMonitor.setAvgPowerConsumed(100f); + expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingWithOngoingAnimation() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); + mTestHandler.flush(); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + + mPmicMonitor.setAvgPowerConsumed(100f); + expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT); + mTestHandler.flush(); + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE); + + mPmicMonitor.setAvgPowerConsumed(100f); + // No cap applied for Temperature.THROTTLING_NONE + expectedBrightness = PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + + // clamper should not be active anymore. + assertFalse(mClamper.isActive()); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + + private static class TestPmicMonitor extends PmicMonitor { + private Temperature mCurrentTemperature; + private final PowerChangeListener mListener; + TestPmicMonitor(PowerChangeListener listener, int pollingTime) { + super(listener, pollingTime); + mListener = listener; + } + public void setAvgPowerConsumed(float power) { + int status = mCurrentTemperature.getStatus(); + mListener.onChanged(power, status); + } + public void setThermalStatus(@Temperature.ThrottlingStatus int status) { + mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status); + } + } + + private class TestInjector extends BrightnessPowerClamper.Injector { + @Override + TestPmicMonitor getPmicMonitor(PowerChangeListener listener, + int pollingTime) { + mPmicMonitor = new TestPmicMonitor(listener, pollingTime); + return mPmicMonitor; + } + + @Override + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); + } + } + + private static class TestPowerData implements BrightnessPowerClamper.PowerData { + + private final String mUniqueDisplayId; + private final String mDataId; + private final PowerThrottlingData mData; + private final PowerThrottlingConfigData mConfigData; + + private TestPowerData() { + this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null); + } + + private TestPowerData(List<ThrottlingLevel> data) { + this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data); + } + + private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) { + mUniqueDisplayId = uniqueDisplayId; + mDataId = dataId; + mData = PowerThrottlingData.create(data); + mConfigData = new PowerThrottlingConfigData(0.1f, 10); + } + + @NonNull + @Override + public String getUniqueDisplayId() { + return mUniqueDisplayId; + } + + @NonNull + @Override + public String getPowerThrottlingDataId() { + return mDataId; + } + + @Nullable + @Override + public PowerThrottlingData getPowerThrottlingData() { + return mData; + } + + @Nullable + @Override + public PowerThrottlingConfigData getPowerThrottlingConfigData() { + return mConfigData; + } + } +} 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 c7c09b5deb35..ec27f9d220dc 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/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index c4f72b307eb7..6a95d5c57024 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -102,6 +102,9 @@ import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.testutils.FakeDeviceConfigInterface; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -121,26 +124,28 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayModeDirectorTest { public static Collection<Object[]> getAppRequestedSizeTestCases() { var appRequestedSizeTestCases = Arrays.asList(new Object[][] { - {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY, - DEFAULT_MODE_75.getRefreshRate(), Map.of()}, - {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY, - APP_MODE_HIGH_90.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(), + /*votesWithPriorities*/ Map.of()}, + {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))}, - {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY, + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), @@ -149,9 +154,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -160,9 +166,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -173,10 +180,12 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), false}, - {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(), - APP_MODE_65.getRefreshRate(), - Map.of( + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -187,7 +196,40 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), true}}); + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ true}, + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65 + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ true}}); final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); @@ -218,6 +260,8 @@ public class DisplayModeDirectorTest { private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode( + /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60); private static final Display.Mode APP_MODE_65 = new Display.Mode( /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65); private static final Display.Mode LIMIT_MODE_70 = new Display.Mode( @@ -227,8 +271,7 @@ public class DisplayModeDirectorTest { private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode( /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90); private static final Display.Mode[] TEST_MODES = new Display.Mode[] { - new Display.Mode( - /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60), + DEFAULT_MODE_60, APP_MODE_65, LIMIT_MODE_70, DEFAULT_MODE_75, 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 new file mode 100644 index 000000000000..d5a92cbc927f --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java @@ -0,0 +1,130 @@ +/* + * 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 new file mode 100644 index 000000000000..1d2034be4acb --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.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.display.notifications; + +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager.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.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DisplayNotificationManager} + * Run: atest DisplayNotificationManagerTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class DisplayNotificationManagerTest { + @Mock + private Injector mMockedInjector; + @Mock + private NotificationManager mMockedNotificationManager; + @Mock + private DisplayManagerFlags mMockedFlags; + @Captor + private ArgumentCaptor<String> mNotifyTagCaptor; + @Captor + private ArgumentCaptor<Integer> mNotifyNoteIdCaptor; + @Captor + private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor; + + /** Setup tests. */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNotificationOnHotplugConnectionError() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onHotplugConnectionError(); + 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 */ + 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()); + } + + private DisplayNotificationManager createDisplayNotificationManager( + final boolean isNotificationManagerAvailable, + final boolean isErrorHandlingEnabled) { + when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn( + 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(); + return displayNotificationManager; + } + + private void assertExpectedNotification() { + verify(mMockedNotificationManager).notify( + mNotifyTagCaptor.capture(), + mNotifyNoteIdCaptor.capture(), + mNotifyAsUserNotificationCaptor.capture()); + assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager"); + assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1); + final var notification = mNotifyAsUserNotificationCaptor.getValue(); + assertThat(notification.getChannelId()).isEqualTo("ALERTS"); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR); + assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC); + assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0); + assertThat(notification.when).isEqualTo(0); + assertThat(notification.getTimeoutAfter()).isEqualTo(30000L); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 5b519633081a..21e3b34ed61a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -62,8 +62,6 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED; @@ -75,7 +73,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW; -import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; @@ -85,7 +82,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP; -import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE; import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -799,47 +795,6 @@ public final class AlarmManagerServiceTest { } @Test - public void updatingExactAlarmDenyList() { - ArraySet<String> denyListed = new ArraySet<>(new String[]{ - "com.example.package1", - "com.example.package2", - "com.example.package3", - }); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, - "com.example.package1,com.example.package2,com.example.package3"); - assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - - - denyListed = new ArraySet<>(new String[]{ - "com.example.package1", - "com.example.package4", - }); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, - "com.example.package1,com.example.package4"); - assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); - assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size()); - } - - @Test - public void exactAlarmDenyListMaxSize() { - final ArraySet<String> expectedSet = new ArraySet<>(); - final StringBuilder sb = new StringBuilder("package1"); - expectedSet.add("package1"); - for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) { - sb.append(",package"); - sb.append(i); - if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) { - expectedSet.add("package" + i); - } - } - assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size()); - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString()); - assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST); - } - - @Test public void positiveWhileIdleQuotas() { setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); @@ -2212,50 +2167,30 @@ public final class AlarmManagerServiceTest { } @Test - public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException { + public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED); + mockScheduleExactAlarmStatePreT(true, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test - public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); - assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - } - - @Test public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT); - assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - - mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @@ -2281,41 +2216,33 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); // No permission, no exemption. - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - - // No permission, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Policy permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockUseExactAlarmState(false); // User permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); - assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - - // User permission only, no exemption. - mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false); doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID)); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Both permissions and exemption. - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); } @@ -2514,17 +2441,12 @@ public final class AlarmManagerServiceTest { assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } - private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) { + private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) { String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING; when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM)) .thenReturn(requesters); mService.refreshExactAlarmCandidates(); - if (denyList) { - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); - } else { - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); - } when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(mode); } @@ -2556,7 +2478,7 @@ public final class AlarmManagerServiceTest { public void alarmClockBinderCallWithoutPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2630,7 +2552,7 @@ public final class AlarmManagerServiceTest { public void exactBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2650,7 +2572,7 @@ public final class AlarmManagerServiceTest { public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2676,7 +2598,7 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockUseExactAlarmState(true); - mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(false, MODE_ERRORED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2700,7 +2622,7 @@ public final class AlarmManagerServiceTest { public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2726,7 +2648,7 @@ public final class AlarmManagerServiceTest { public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2836,7 +2758,7 @@ public final class AlarmManagerServiceTest { public void binderCallWithUserAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); @@ -3052,135 +2974,11 @@ public final class AlarmManagerServiceTest { } @Test - public void denyListChanged() { - mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT); - - setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); - - final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); - verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(), - anyLong()); - - final List<Message> messages = messageCaptor.getAllValues(); - for (final Message msg : messages) { - assertTrue("Unwanted message sent to handler: " + msg.what, - msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED - || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED); - mService.mHandler.handleMessage(msg); - } - - ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"}); - verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true)); - - ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"}); - verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false)); - } - - @Test - public void permissionGrantedDueToDenyList() { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - final String[] packages = {"example.package.1", "example.package.2"}; - - final int appId1 = 232; - final int appId2 = 431; - - final int userId1 = 42; - final int userId2 = 53; - - registerAppIds(packages, new Integer[]{appId1, appId2}); - - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - - when(mPermissionManagerInternal.getAppOpPermissionPackages( - SCHEDULE_EXACT_ALARM)).thenReturn(packages); - mService.refreshExactAlarmCandidates(); - - final long allowListDuration = 53442; - when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn( - allowListDuration); - - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - - mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); - - // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), - anyBoolean()); - - // Permission got granted only for (appId1, userId2). - final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); - final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class); - - verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(), - isNull(), bundleCaptor.capture()); - - assertEquals(userId2, userCaptor.getValue().getIdentifier()); - - // Validate the intent. - assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, - intentCaptor.getValue().getAction()); - assertEquals(packages[0], intentCaptor.getValue().getPackage()); - - // Validate the options. - final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); - assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, - bOptions.getTemporaryAppAllowlistType()); - assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration()); - assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, - bOptions.getTemporaryAppAllowlistReasonCode()); - } - - @Test - public void permissionRevokedDueToDenyList() { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - - final String[] packages = {"example.package.1", "example.package.2"}; - - final int appId1 = 232; - final int appId2 = 431; - - final int userId1 = 42; - final int userId2 = 53; - - registerAppIds(packages, new Integer[]{appId1, appId2}); - - when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - - when(mPermissionManagerInternal.getAppOpPermissionPackages( - SCHEDULE_EXACT_ALARM)).thenReturn(packages); - mService.refreshExactAlarmCandidates(); - - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); - mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - - mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); - - // Permission got revoked only for (appId1, userId2) - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); - - verify(mService).removeExactAlarmsOnPermissionRevoked( - eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); - } - - @Test public void opChangedPermissionRevoked() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); @@ -3193,20 +2991,7 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); - - mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); - - verify(mService.mHandler, never()).sendMessageAtTime( - argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); - } - - @Test - public void opChangedNoPermissionChangeDueToDenyList() throws Exception { - mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); - - mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); @@ -3222,7 +3007,7 @@ public final class AlarmManagerServiceTest { when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -3482,7 +3267,7 @@ public final class AlarmManagerServiceTest { .putExtra(Intent.EXTRA_REPLACING, true); mockUseExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3490,7 +3275,7 @@ public final class AlarmManagerServiceTest { assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3498,7 +3283,7 @@ public final class AlarmManagerServiceTest { assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3653,16 +3438,16 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); - mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); - assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); + assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @@ -3671,11 +3456,11 @@ public final class AlarmManagerServiceTest { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mockScheduleExactAlarmState(true); - mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); + mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmState(false); - mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); + mockScheduleExactAlarmStatePreT(true, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java index 596a3f3d0400..b3605ccfc25d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -280,7 +280,9 @@ public class AsyncProcessStartTest { 0, 0); // Sleep until timeout should have triggered - SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); + if (wedge) { + SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); + } return app; } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 410ae35aa790..367e14b37180 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -228,7 +228,7 @@ public class BroadcastQueueTest { LocalServices.removeServiceForTest(AlarmManagerInternal.class); LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); - doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); + doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any()); doAnswer((invocation) -> { return getUidForPackage(invocation.getArgument(0)); }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); @@ -1014,8 +1014,9 @@ public class BroadcastQueueTest { eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER)); // Confirm that we unstopped manifest receivers - verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState( - eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM)); + verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed( + eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM), + eq(callerApp.info.packageName), any()); } // Confirm that we've reported expected usage events diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java index 1ce79a5b596b..05ac5b5720e6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -16,8 +16,6 @@ package com.android.server.display; -import static android.os.Process.INVALID_UID; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -35,7 +33,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal; import org.junit.Before; import org.junit.Rule; @@ -55,7 +53,10 @@ public class SmallAreaDetectionControllerTest { @Mock private PackageManagerInternal mMockPackageManagerInternal; @Mock - private UserManagerInternal mMockUserManagerInternal; + private PackageStateInternal mMockPkgStateA; + @Mock + private PackageStateInternal mMockPkgStateB; + private SmallAreaDetectionController mSmallAreaDetectionController; @@ -64,29 +65,18 @@ public class SmallAreaDetectionControllerTest { private static final String PKG_NOT_INSTALLED = "com.not.installed"; private static final float THRESHOLD_A = 0.05f; private static final float THRESHOLD_B = 0.07f; - private static final int USER_1 = 110; - private static final int USER_2 = 111; - private static final int UID_A_1 = 11011111; - private static final int UID_A_2 = 11111111; - private static final int UID_B_1 = 11022222; - private static final int UID_B_2 = 11122222; + private static final int APP_ID_A = 11111; + private static final int APP_ID_B = 22222; @Before public void setup() { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); - LocalServices.removeServiceForTest(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); - - when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); - when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); - when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); - when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); - when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); - when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( - INVALID_UID); - when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( - INVALID_UID); + + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA); + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB); + when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A); + when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B); mSmallAreaDetectionController = spy(new SmallAreaDetectionController( new ContextWrapper(ApplicationProvider.getApplicationContext()), @@ -99,9 +89,9 @@ public class SmallAreaDetectionControllerTest { final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; - final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_A, APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -110,9 +100,9 @@ public class SmallAreaDetectionControllerTest { final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_B_1, UID_B_2}; - final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -122,9 +112,9 @@ public class SmallAreaDetectionControllerTest { PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_A_1, UID_A_2}; - final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_A}; + final float[] resultThresholdArray = {THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 7cc01e1b4292..4329b6fbc8e3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -160,6 +160,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); + setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); + setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index eb50556821eb..610ea903767e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -427,6 +428,7 @@ public class PackageArchiverTest { for (LauncherActivityInfo mainActivity : createLauncherActivities()) { ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( mainActivity.getLabel().toString(), + mainActivity.getComponentName(), ICON_PATH, null); activityInfos.add(activityInfo); } @@ -437,9 +439,11 @@ public class PackageArchiverTest { ActivityInfo activityInfo = mock(ActivityInfo.class); LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class); when(activity1.getLabel()).thenReturn("activity1"); + when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1")); when(activity1.getActivityInfo()).thenReturn(activityInfo); LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class); when(activity2.getLabel()).thenReturn("activity2"); + when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2")); when(activity2.getActivityInfo()).thenReturn(activityInfo); return List.of(activity1, activity2); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java new file mode 100644 index 000000000000..2003d04e1dbc --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -0,0 +1,201 @@ +/* + * 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.BatteryConsumer; +import android.os.PersistableBundle; +import android.util.Xml; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.text.ParseException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AggregatedPowerStatsTest { + private static final int TEST_POWER_COMPONENT = 1077; + private static final int APP_1 = 27; + private static final int APP_2 = 42; + + private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + private PowerStats.Descriptor mPowerComponentDescriptor; + + @Before + public void setup() throws ParseException { + mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); + mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + + mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3, + PersistableBundle.forPair("speed", "fast")); + } + + @Test + public void aggregation() { + AggregatedPowerStats stats = prepareAggregatePowerStats(); + + verifyAggregatedPowerStats(stats); + } + + @Test + public void xmlPersistence() throws Exception { + AggregatedPowerStats stats = prepareAggregatePowerStats(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newFastSerializer(); + serializer.setOutput(baos, "UTF-8"); + stats.writeXml(serializer); + serializer.flush(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8"); + AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser, + mAggregatedPowerStatsConfig); + + verifyAggregatedPowerStats(actualStats); + } + + private AggregatedPowerStats prepareAggregatePowerStats() { + AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); + stats.addClockUpdate(1000, 456); + stats.setDuration(789); + + stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000); + stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, + BatteryConsumer.PROCESS_STATE_CACHED, 2000); + stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, + BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000); + + PowerStats ps = new PowerStats(mPowerComponentDescriptor); + ps.stats[0] = 100; + ps.stats[1] = 987; + + ps.uidStats.put(APP_1, new long[]{389, 0, 739}); + ps.uidStats.put(APP_2, new long[]{278, 314, 628}); + + stats.addPowerStats(ps, 3000); + + stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000); + stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, + BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000); + + ps.stats[0] = 444; + ps.stats[1] = 0; + + ps.uidStats.put(APP_1, new long[]{0, 0, 400}); + ps.uidStats.put(APP_2, new long[]{100, 200, 300}); + + stats.addPowerStats(ps, 5000); + + return stats; + } + + private void verifyAggregatedPowerStats(AggregatedPowerStats stats) { + PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT) + .getPowerStatsDescriptor(); + assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT); + assertThat(descriptor.name).isEqualTo("fan"); + assertThat(descriptor.statsArrayLength).isEqualTo(2); + assertThat(descriptor.uidStatsArrayLength).isEqualTo(3); + assertThat(descriptor.extras.getString("speed")).isEqualTo("fast"); + + assertThat(getDeviceStats(stats, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON)) + .isEqualTo(new long[]{322, 987}); + + assertThat(getDeviceStats(stats, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) + .isEqualTo(new long[]{222, 0}); + + assertThat(getUidDeviceStats(stats, + APP_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) + .isEqualTo(new long[]{259, 0, 492}); + + assertThat(getUidDeviceStats(stats, + APP_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, + BatteryConsumer.PROCESS_STATE_CACHED)) + .isEqualTo(new long[]{129, 0, 446}); + + assertThat(getUidDeviceStats(stats, + APP_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, + BatteryConsumer.PROCESS_STATE_CACHED)) + .isEqualTo(new long[]{0, 0, 200}); + + assertThat(getUidDeviceStats(stats, + APP_2, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) + .isEqualTo(new long[]{185, 209, 418}); + + assertThat(getUidDeviceStats(stats, + APP_2, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, + BatteryConsumer.PROCESS_STATE_FOREGROUND)) + .isEqualTo(new long[]{142, 204, 359}); + + assertThat(getUidDeviceStats(stats, + APP_2, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, + BatteryConsumer.PROCESS_STATE_BACKGROUND)) + .isEqualTo(new long[]{50, 100, 150}); + } + + private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) { + long[] out = new long[states.length]; + stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states); + return out; + } + + private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) { + long[] out = new long[states.length]; + stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states); + return out; + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 5a2d2e3d33fd..663af5da48d2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -42,6 +42,7 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; +import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; @@ -214,6 +215,7 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { + super(Clock.SYSTEM_CLOCK, null); mPowerProfile = new PowerProfile(context, true /* forTest */); SparseArray<int[]> cpusByPolicy = new SparseArray<>(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 77124d0120f7..abb3be7b215b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java @@ -113,15 +113,15 @@ public class BatteryStatsHistoryIteratorTest { public void constrainedIteration() { prepareHistory(); - // Initial time is 3000 + // Initial time is 1000_000 assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0), - 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L); - assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0), - 1003_000L, 2003_000L, 2004_000L); - assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L), - 3_000L, 3_000L, 1003_000L); + 1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L); + assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0), + 2000_000L, 3000_000L, 3001_000L); + assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L), + 1000_000L, 1000_000L, 2000_000L); assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L), - 1003_000L, 2003_000L); + 2000_000L); } private void prepareHistory() { @@ -144,7 +144,7 @@ public class BatteryStatsHistoryIteratorTest { ArrayList<Long> actualTimestamps = new ArrayList<>(); while (iterator.hasNext()) { BatteryStats.HistoryItem item = iterator.next(); - actualTimestamps.add(item.currentTime); + actualTimestamps.add(item.time); } assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps)); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index f22296a6261c..1dd499c99c0c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -41,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerStats; import org.junit.Before; @@ -69,6 +70,7 @@ public class BatteryStatsHistoryTest { private File mSystemDir; private File mHistoryDir; private final MockClock mClock = new MockClock(); + private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); private BatteryStatsHistory mHistory; private BatteryStats.HistoryPrinter mHistoryPrinter; @Mock @@ -94,7 +96,7 @@ public class BatteryStatsHistoryTest { mClock.realtime = 123; mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - mStepDetailsCalculator, mClock, mTracer) { + mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) { @Override public boolean readFileToParcel(Parcel out, AtomicFile file) { mReadFiles.add(file.getBaseFile().getName()); @@ -210,7 +212,7 @@ public class BatteryStatsHistoryTest { mClock.realtime = 1000 * i; fileList.add(mClock.realtime + ".bh"); - mHistory.startNextFile(); + mHistory.startNextFile(mClock.realtime); createActiveFile(mHistory); verifyFileNames(mHistory, fileList); verifyActiveFile(mHistory, mClock.realtime + ".bh"); @@ -218,7 +220,7 @@ public class BatteryStatsHistoryTest { // create file 32 mClock.realtime = 1000 * 32; - mHistory.startNextFile(); + mHistory.startNextFile(mClock.realtime); createActiveFile(mHistory); fileList.add("32000.bh"); fileList.remove(0); @@ -229,7 +231,7 @@ public class BatteryStatsHistoryTest { // create file 33 mClock.realtime = 1000 * 33; - mHistory.startNextFile(); + mHistory.startNextFile(mClock.realtime); createActiveFile(mHistory); // verify file 1 is deleted fileList.add("33000.bh"); @@ -240,7 +242,7 @@ public class BatteryStatsHistoryTest { // create a new BatteryStatsHistory object, it will pick up existing history files. BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock, mTracer); + null, mClock, mMonotonicClock, mTracer); // verify constructor can pick up all files from file system. verifyFileNames(history2, fileList); verifyActiveFile(history2, "33000.bh"); @@ -262,7 +264,7 @@ public class BatteryStatsHistoryTest { // create file 1. mClock.realtime = 2345678; - history2.startNextFile(); + history2.startNextFile(mClock.realtime); createActiveFile(history2); verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh")); verifyActiveFile(history2, "2345678.bh"); @@ -336,14 +338,14 @@ public class BatteryStatsHistoryTest { mHistory.recordEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42); - mHistory.startNextFile(); // 1000.bh + mHistory.startNextFile(mClock.realtime); // 1000.bh mClock.realtime = 2000; mClock.uptime = 2000; mHistory.recordEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42); - mHistory.startNextFile(); // 2000.bh + mHistory.startNextFile(mClock.realtime); // 2000.bh mClock.realtime = 3000; mClock.uptime = 3000; @@ -351,7 +353,7 @@ public class BatteryStatsHistoryTest { HistoryItem.EVENT_ALARM, "alarm", 42); // Flush accumulated history to disk - mHistory.startNextFile(); + mHistory.startNextFile(mClock.realtime); } private void verifyActiveFile(BatteryStatsHistory history, String file) { @@ -518,7 +520,7 @@ public class BatteryStatsHistoryTest { // Keep the preserved part of history short - we only need to capture the very tail of // history. mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000, - mStepDetailsCalculator, mClock, mTracer); + mStepDetailsCalculator, mClock, mMonotonicClock, mTracer); mHistory.forceRecordAllHistory(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 5df0acb65249..b1da1fc88378 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import org.junit.Rule; @@ -64,6 +65,8 @@ public class BatteryUsageStatsProviderTest { new BatteryUsageStatsRule(12345, mHistoryDir) .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0) .setAveragePower(PowerProfile.POWER_AUDIO, 720.0); + private MockClock mMockClock = mStatsRule.getMockClock(); + @Test public void test_getBatteryUsageStats() { BatteryStatsImpl batteryStats = prepareBatteryStats(); @@ -369,17 +372,23 @@ public class BatteryUsageStatsProviderTest { public void testAggregateBatteryStats() { Context context = InstrumentationRegistry.getContext(); BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - mStatsRule.setCurrentTime(5 * MINUTE_IN_MS); + MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock()); + + setTime(5 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } - BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context, - batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), - new TestHandler(), Integer.MAX_VALUE); - batteryUsageStatsStore.onSystemReady(); + + PowerStatsStore powerStatsStore = new PowerStatsStore( + new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), + new TestHandler(), null); BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, batteryUsageStatsStore); + batteryStats, powerStatsStore); + + batteryStats.setBatteryResetListener(reason -> + powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(), + provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT))); synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, @@ -389,7 +398,7 @@ public class BatteryUsageStatsProviderTest { batteryStats.noteFlashlightOffLocked(APP_UID, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); } - mStatsRule.setCurrentTime(25 * MINUTE_IN_MS); + setTime(25 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } @@ -402,7 +411,7 @@ public class BatteryUsageStatsProviderTest { batteryStats.noteFlashlightOffLocked(APP_UID, 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); } - mStatsRule.setCurrentTime(55 * MINUTE_IN_MS); + setTime(55 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } @@ -416,7 +425,7 @@ public class BatteryUsageStatsProviderTest { batteryStats.noteFlashlightOffLocked(APP_UID, 70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS); } - mStatsRule.setCurrentTime(75 * MINUTE_IN_MS); + setTime(75 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } @@ -430,7 +439,7 @@ public class BatteryUsageStatsProviderTest { batteryStats.noteFlashlightOffLocked(APP_UID, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); } - mStatsRule.setCurrentTime(95 * MINUTE_IN_MS); + setTime(95 * MINUTE_IN_MS); // Include the first and the second snapshot, but not the third or current BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() @@ -457,29 +466,41 @@ public class BatteryUsageStatsProviderTest { .of(180.0); } + private void setTime(long timeMs) { + mMockClock.currentTime = timeMs; + mMockClock.realtime = timeMs; + } + @Test public void testAggregateBatteryStats_incompatibleSnapshot() { Context context = InstrumentationRegistry.getContext(); MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"}); - BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class); - - when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps()) - .thenReturn(new long[]{1000, 2000}); + PowerStatsStore powerStatsStore = mock(PowerStatsStore.class); - when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn( + PowerStatsSpan span0 = new PowerStatsSpan(0); + span0.addTimeFrame(0, 1000, 1234); + span0.addSection(new BatteryUsageStatsSection( new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames()) - .setStatsDuration(1234).build()); + .setStatsDuration(1234).build())); - // Add a snapshot, with a different set of custom power components. It should - // be skipped by the aggregation. - when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn( + PowerStatsSpan span1 = new PowerStatsSpan(1); + span1.addTimeFrame(0, 2000, 4321); + span1.addSection(new BatteryUsageStatsSection( new BatteryUsageStats.Builder(new String[]{"different"}) - .setStatsDuration(4321).build()); + .setStatsDuration(4321).build())); + + when(powerStatsStore.getTableOfContents()).thenReturn( + List.of(span0.getMetadata(), span1.getMetadata())); + + when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE)) + .thenReturn(span0); + when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE)) + .thenReturn(span1); BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, batteryUsageStatsStore); + batteryStats, powerStatsStore); BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(0, 3000) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 93cbea6125fa..3579fce11c8d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -88,6 +88,10 @@ public class BatteryUsageStatsRule implements TestRule { mBatteryStats.onSystemReady(); } + public MockClock getMockClock() { + return mMockClock; + } + public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) { mPowerProfile.forceInitForTesting(mContext, xmlId); return this; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java deleted file mode 100644 index b846e3a36656..000000000000 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power.stats; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; - -import android.content.Context; -import android.os.BatteryManager; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Xml; - -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.os.PowerProfile; -import com.android.modules.utils.TypedXmlSerializer; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -@SuppressWarnings("GuardedBy") -public class BatteryUsageStatsStoreTest { - private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; - - private final MockClock mMockClock = new MockClock(); - private MockBatteryStatsImpl mBatteryStats; - private BatteryUsageStatsStore mBatteryUsageStatsStore; - private BatteryUsageStatsProvider mBatteryUsageStatsProvider; - private File mStoreDirectory; - - @Before - public void setup() { - mMockClock.currentTime = 123; - mBatteryStats = new MockBatteryStatsImpl(mMockClock); - mBatteryStats.setNoAutoReset(true); - mBatteryStats.setPowerProfile(mock(PowerProfile.class)); - mBatteryStats.onSystemReady(); - - Context context = InstrumentationRegistry.getContext(); - - mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest"); - clearDirectory(mStoreDirectory); - - mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats, - mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); - mBatteryUsageStatsStore.onSystemReady(); - - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); - } - - @Test - public void testStoreSnapshot() { - mMockClock.currentTime = 1_600_000; - mMockClock.realtime = 1000; - mMockClock.uptime = 1000; - - prepareBatteryStats(); - - mMockClock.realtime = 1_000_000; - mMockClock.uptime = 1_000_000; - mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - - final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); - assertThat(timestamps).hasLength(1); - assertThat(timestamps[0]).isEqualTo(1_600_000); - - final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats( - 1_600_000); - assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123); - assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000); - assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); - assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5); - assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000); - assertThat(batteryUsageStats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower()) - .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000 - } - - @Test - public void testGarbageCollectOldSnapshots() throws Exception { - prepareBatteryStats(); - - mMockClock.realtime = 10_000_000; - mMockClock.uptime = 10_000_000; - mMockClock.currentTime = 10_000_000; - - final int snapshotFileSize = getSnapshotFileSize(); - final int numberOfSnapshots = - (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize); - for (int i = 0; i < numberOfSnapshots + 2; i++) { - mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - - mMockClock.realtime += 10_000_000; - mMockClock.uptime += 10_000_000; - mMockClock.currentTime += 10_000_000; - prepareBatteryStats(); - } - - final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); - Arrays.sort(timestamps); - assertThat(timestamps).hasLength(numberOfSnapshots); - // Two snapshots (10_000_000 and 20_000_000) should have been discarded - assertThat(timestamps[0]).isEqualTo(30_000_000); - assertThat(getDirectorySize(mStoreDirectory)) - .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); - } - - @Test - public void testRemoveAllSnapshots() throws Exception { - prepareBatteryStats(); - - for (int i = 0; i < 3; i++) { - mMockClock.realtime += 10_000_000; - mMockClock.uptime += 10_000_000; - mMockClock.currentTime += 10_000_000; - prepareBatteryStats(); - - mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - } - - assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0); - - mBatteryUsageStatsStore.removeAllSnapshots(); - - assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0); - } - - @Test - public void testSavingStatsdAtomPullTimestamp() { - mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); - assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) - .isEqualTo(1234); - mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); - assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) - .isEqualTo(5478); - } - - private void prepareBatteryStats() { - mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, - /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, - mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime); - mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, - /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0, - mMockClock.realtime + 500_000, mMockClock.uptime + 500_000, - mMockClock.currentTime + 500_000); - } - - private void clearDirectory(File dir) { - if (dir.exists()) { - for (File child : dir.listFiles()) { - if (child.isDirectory()) { - clearDirectory(child); - } - child.delete(); - } - } - } - - private long getDirectorySize(File dir) { - long size = 0; - if (dir.exists()) { - for (File child : dir.listFiles()) { - if (child.isDirectory()) { - size += getDirectorySize(child); - } else { - size += child.length(); - } - } - } - return size; - } - - private int getSnapshotFileSize() throws IOException { - BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats( - new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0) - .includePowerModels() - .build()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TypedXmlSerializer serializer = Xml.newBinarySerializer(); - serializer.setOutput(out, StandardCharsets.UTF_8.name()); - serializer.startDocument(null, true); - stats.writeXml(serializer); - serializer.endDocument(); - return out.toByteArray().length; - } - - private static class TestHandler extends Handler { - TestHandler() { - super(Looper.getMainLooper()); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - msg.getCallback().run(); - return true; - } - } -} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index 4ecee9fe7d23..30a731818f36 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -122,7 +122,7 @@ public class MultiStateStatsTest { // 4 * 10 = 40 bits needed to represent the composite state MultiStateStats.States[] states = new MultiStateStats.States[10]; for (int i = 0; i < states.length; i++) { - states[i] = new MultiStateStats.States(true, labels); + states[i] = new MultiStateStats.States("foo", true, labels); } IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new MultiStateStats.Factory(DIMENSION_COUNT, states)); @@ -191,8 +191,8 @@ public class MultiStateStatsTest { private static MultiStateStats.Factory makeFactory(boolean trackBatteryState, boolean trackProcState, boolean trackScreenState) { return new MultiStateStats.Factory(DIMENSION_COUNT, - new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"), - new MultiStateStats.States(trackProcState, + new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"), + new MultiStateStats.States("ps", trackProcState, BatteryConsumer.processStateToString( BatteryConsumer.PROCESS_STATE_UNSPECIFIED), BatteryConsumer.processStateToString( @@ -203,7 +203,7 @@ public class MultiStateStatsTest { BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE), BatteryConsumer.processStateToString( BatteryConsumer.PROCESS_STATE_CACHED)), - new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in")); + new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in")); } private FactorySubject assertThatCpuPerformanceStatsFactory( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index 47de44324ae1..b52fc8a7c727 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -24,11 +24,14 @@ import static org.mockito.Mockito.mock; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.PersistableBundle; +import android.text.format.DateFormat; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistory; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerStats; import org.junit.Before; @@ -36,16 +39,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.TimeZone; @RunWith(AndroidJUnit4.class) @SmallTest public class PowerStatsAggregatorTest { private static final int TEST_POWER_COMPONENT = 77; private static final int TEST_UID = 42; + private static final long START_TIME = 1234; private final MockClock mClock = new MockClock(); - private long mStartTime; + private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock); private BatteryStatsHistory mHistory; private PowerStatsAggregator mAggregator; private int mAggregatedStatsCount; @@ -53,25 +59,25 @@ public class PowerStatsAggregatorTest { @Before public void setup() throws ParseException { mHistory = new BatteryStatsHistory(32, 1024, - mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock); - mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm") - .parse("2008-09-23 08:00").getTime(); - mClock.currentTime = mStartTime; + mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, + mMonotonicClock); - PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory); - builder.trackPowerComponent(TEST_POWER_COMPONENT) + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackPowerComponent(TEST_POWER_COMPONENT) .trackDeviceStates( - PowerStatsAggregator.STATE_POWER, - PowerStatsAggregator.STATE_SCREEN) + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) .trackUidStates( - PowerStatsAggregator.STATE_POWER, - PowerStatsAggregator.STATE_SCREEN, - PowerStatsAggregator.STATE_PROCESS_STATE); - mAggregator = builder.build(); + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + mAggregator = new PowerStatsAggregator(config, mHistory); } @Test public void stateUpdates() { + mClock.currentTime = 1222156800000L; // An important date in world history + mHistory.forceRecordAllHistory(); mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true); mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime, @@ -98,15 +104,32 @@ public class PowerStatsAggregatorTest { mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID, BatteryConsumer.PROCESS_STATE_BACKGROUND); - advance(3000); + advance(1000); + + mClock.currentTime += 60 * 60 * 1000; // one hour + mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime); + + advance(2000); powerStats.stats = new long[]{20000}; powerStats.uidStats.put(TEST_UID, new long[]{4444}); mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); - mAggregator.aggregateBatteryStats(0, 0, stats -> { + mAggregator.aggregatePowerStats(0, 0, stats -> { assertThat(mAggregatedStatsCount++).isEqualTo(0); - assertThat(stats.getStartTime()).isEqualTo(mStartTime); + assertThat(stats.getStartTime()).isEqualTo(START_TIME); + + List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates(); + assertThat(clockUpdates).hasSize(2); + + AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0); + assertThat(clockUpdate0.monotonicTime).isEqualTo(1234); + assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00"); + + AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1); + assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000); + assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03"); + assertThat(stats.getDuration()).isEqualTo(5000); long[] values = new long[1]; @@ -115,40 +138,47 @@ public class PowerStatsAggregatorTest { TEST_POWER_COMPONENT); assertThat(powerComponentStats.getDeviceStats(values, new int[]{ - PowerStatsAggregator.POWER_STATE_OTHER, - PowerStatsAggregator.SCREEN_STATE_ON})) + AggregatedPowerStatsConfig.POWER_STATE_OTHER, + AggregatedPowerStatsConfig.SCREEN_STATE_ON})) .isTrue(); assertThat(values).isEqualTo(new long[]{10000}); assertThat(powerComponentStats.getDeviceStats(values, new int[]{ - PowerStatsAggregator.POWER_STATE_BATTERY, - PowerStatsAggregator.SCREEN_STATE_OTHER})) + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER})) .isTrue(); assertThat(values).isEqualTo(new long[]{20000}); assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ - PowerStatsAggregator.POWER_STATE_OTHER, - PowerStatsAggregator.SCREEN_STATE_ON, + AggregatedPowerStatsConfig.POWER_STATE_OTHER, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_FOREGROUND})) .isTrue(); assertThat(values).isEqualTo(new long[]{1234}); assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ - PowerStatsAggregator.POWER_STATE_BATTERY, - PowerStatsAggregator.SCREEN_STATE_OTHER, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, BatteryConsumer.PROCESS_STATE_FOREGROUND})) .isTrue(); assertThat(values).isEqualTo(new long[]{1111}); assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ - PowerStatsAggregator.POWER_STATE_BATTERY, - PowerStatsAggregator.SCREEN_STATE_OTHER, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, BatteryConsumer.PROCESS_STATE_BACKGROUND})) .isTrue(); assertThat(values).isEqualTo(new long[]{3333}); }); } + @NonNull + private static CharSequence formatDateTime(long timeInMillis) { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + cal.setTimeInMillis(timeInMillis); + return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal); + } + @Test public void incompatiblePowerStats() { mHistory.forceRecordAllHistory(); @@ -181,39 +211,39 @@ public class PowerStatsAggregatorTest { mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true); - mAggregator.aggregateBatteryStats(0, 0, stats -> { + mAggregator.aggregatePowerStats(0, 0, stats -> { long[] values = new long[1]; PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(TEST_POWER_COMPONENT); if (mAggregatedStatsCount == 0) { - assertThat(stats.getStartTime()).isEqualTo(mStartTime); + assertThat(stats.getStartTime()).isEqualTo(START_TIME); assertThat(stats.getDuration()).isEqualTo(2000); assertThat(powerComponentStats.getDeviceStats(values, new int[]{ - PowerStatsAggregator.POWER_STATE_OTHER, - PowerStatsAggregator.SCREEN_STATE_ON})) + AggregatedPowerStatsConfig.POWER_STATE_OTHER, + AggregatedPowerStatsConfig.SCREEN_STATE_ON})) .isTrue(); assertThat(values).isEqualTo(new long[]{10000}); assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ - PowerStatsAggregator.POWER_STATE_OTHER, - PowerStatsAggregator.SCREEN_STATE_ON, + AggregatedPowerStatsConfig.POWER_STATE_OTHER, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_FOREGROUND})) .isTrue(); assertThat(values).isEqualTo(new long[]{1234}); } else if (mAggregatedStatsCount == 1) { - assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000); + assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000); assertThat(stats.getDuration()).isEqualTo(1000); assertThat(powerComponentStats.getDeviceStats(values, new int[]{ - PowerStatsAggregator.POWER_STATE_BATTERY, - PowerStatsAggregator.SCREEN_STATE_ON})) + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON})) .isTrue(); assertThat(values).isEqualTo(new long[]{20000}); assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ - PowerStatsAggregator.POWER_STATE_BATTERY, - PowerStatsAggregator.SCREEN_STATE_ON, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON, BatteryConsumer.PROCESS_STATE_FOREGROUND})) .isTrue(); assertThat(values).isEqualTo(new long[]{4444}); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java new file mode 100644 index 000000000000..0e58787a6a9b --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -0,0 +1,269 @@ +/* + * 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.BatteryConsumer; +import android.os.BatteryManager; +import android.os.BatteryUsageStats; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsSchedulerTest { + private PowerStatsStore mPowerStatsStore; + private Handler mHandler; + private MockClock mClock = new MockClock(); + private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); + private MockBatteryStatsImpl mBatteryStats; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private PowerStatsScheduler mPowerStatsScheduler; + private PowerProfile mPowerProfile; + private PowerStatsAggregator mPowerStatsAggregator; + private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + + @Before + public void setup() { + final Context context = InstrumentationRegistry.getContext(); + + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + + mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); + mClock.realtime = 7654321; + + HandlerThread bgThread = new HandlerThread("bg thread"); + bgThread.start(); + File systemDir = context.getCacheDir(); + mHandler = new Handler(bgThread.getLooper()); + mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); + mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig); + mPowerProfile = mock(PowerProfile.class); + when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); + mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); + mPowerStatsAggregator = mock(PowerStatsAggregator.class); + mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, + TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, + mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider); + } + + @Test + @SuppressWarnings("unchecked") + public void storeAggregatePowerStats() { + mPowerStatsStore.reset(); + + assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + + mPowerStatsStore.storeAggregatedPowerStats( + createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime, + 123)); + + long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90); + mClock.realtime += delayBeforeAggregating; + mClock.currentTime += delayBeforeAggregating; + + doAnswer(invocation -> { + // The first span is longer than 30 min, because the end time is being aligned with + // the wall clock. Subsequent spans should be precisely 30 minutes. + long startTime = invocation.getArgument(0); + long endTime = invocation.getArgument(1); + Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2); + + long startTimeWallClock = + mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime); + long endTimeWallClock = + mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); + + assertThat(startTime).isEqualTo(7654321 + 123); + assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30)); + assertThat(Instant.ofEpochMilli(endTimeWallClock)) + .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); + + consumer.accept( + createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime)); + return null; + }).doAnswer(invocation -> { + long startTime = invocation.getArgument(0); + long endTime = invocation.getArgument(1); + Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2); + + long startTimeWallClock = + mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime); + long endTimeWallClock = + mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); + + assertThat(Instant.ofEpochMilli(startTimeWallClock)) + .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); + assertThat(Instant.ofEpochMilli(endTimeWallClock)) + .isEqualTo(Instant.parse("2023-01-02T04:30:00Z")); + + consumer.accept( + createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime)); + return null; + }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(), + any(Consumer.class)); + + mPowerStatsScheduler.schedulePowerStatsAggregation(); + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + + verify(mPowerStatsAggregator, times(2)) + .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); + + List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); + assertThat(contents).hasSize(3); + // Skip the first entry, which was placed in the store at the beginning of this test + PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0); + PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0); + assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123); + assertThat(timeFrame2.startMonotonicTime) + .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration); + assertThat(Instant.ofEpochMilli(timeFrame2.startTime)) + .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); + assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30)); + } + + private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime, + long duration) { + AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); + stats.addClockUpdate(monotonicTime, currentTime); + stats.setDuration(duration); + return stats; + } + + @Test + public void storeBatteryUsageStatsOnReset() { + mBatteryStats.forceRecordAllHistory(); + synchronized (mBatteryStats) { + mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true, + BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); + } + + mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false); + + assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + + mPowerStatsScheduler.start(true); + + synchronized (mBatteryStats) { + mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime); + } + + mClock.realtime += 60000; + mClock.currentTime += 60000; + + synchronized (mBatteryStats) { + mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime); + } + + mClock.realtime += 60000; + mClock.currentTime += 60000; + + // Battery stats reset should have the side-effect of saving accumulated battery usage stats + synchronized (mBatteryStats) { + mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + // Await completion + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + + List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); + assertThat(contents).hasSize(1); + + PowerStatsSpan.Metadata metadata = contents.get(0); + + PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), + BatteryUsageStatsSection.TYPE); + assertThat(span).isNotNull(); + + List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); + assertThat(timeFrames).hasSize(1); + assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); + assertThat(timeFrames.get(0).duration).isEqualTo(120000); + + List<PowerStatsSpan.Section> sections = span.getSections(); + assertThat(sections).hasSize(1); + + PowerStatsSpan.Section section = sections.get(0); + assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); + BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); + assertThat(bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo(60000); + } + + @Test + public void alignToWallClock() { + // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min + assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), + 123 + TimeUnit.HOURS.toMillis(2), + Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo( + 123 + Duration.parse("PT1M30S").toMillis()); + + // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min + assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), + 123 + TimeUnit.HOURS.toMillis(2), + Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo( + 123 + Duration.parse("PT2M45S").toMillis()); + + // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min + assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1), + 123 + TimeUnit.HOURS.toMillis(2), + Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo( + 123 + Duration.parse("PT15S").toMillis()); + + // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec - + // rounded to the next 3 hours + assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3), + 123 + TimeUnit.HOURS.toMillis(9), + Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo( + 123 + Duration.parse("PT1H46M30S").toMillis()); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java new file mode 100644 index 000000000000..d3628b5888c8 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java @@ -0,0 +1,179 @@ +/* + * 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SuppressWarnings("GuardedBy") +public class PowerStatsStoreTest { + private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; + + private PowerStatsStore mPowerStatsStore; + private File mStoreDirectory; + + @Before + public void setup() { + Context context = InstrumentationRegistry.getContext(); + + mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest"); + clearDirectory(mStoreDirectory); + + mPowerStatsStore = new PowerStatsStore(mStoreDirectory, + MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES, + new TestHandler(), + (sectionType, parser) -> { + if (sectionType.equals(TestSection.TYPE)) { + return TestSection.readXml(parser); + } + return null; + }); + } + + @Test + public void garbageCollectOldSpans() throws Exception { + int spanSize = 500; + final int numberOfSnaps = + (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize); + for (int i = 0; i < numberOfSnaps + 2; i++) { + PowerStatsSpan span = new PowerStatsSpan(i); + span.addSection(new TestSection(i, spanSize)); + mPowerStatsStore.storePowerStatsSpan(span); + } + + assertThat(getDirectorySize(mStoreDirectory)) + .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + + List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents(); + assertThat(toc.size()).isLessThan(numberOfSnaps); + int minPreservedSpanId = numberOfSnaps - toc.size(); + for (PowerStatsSpan.Metadata metadata : toc) { + assertThat(metadata.getId()).isAtLeast(minPreservedSpanId); + } + } + + @Test + public void reset() throws Exception { + for (int i = 0; i < 3; i++) { + PowerStatsSpan span = new PowerStatsSpan(i); + span.addSection(new TestSection(i, 42)); + mPowerStatsStore.storePowerStatsSpan(span); + } + + assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0); + + mPowerStatsStore.reset(); + + assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0); + } + + private void clearDirectory(File dir) { + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + clearDirectory(child); + } + child.delete(); + } + } + } + + private long getDirectorySize(File dir) { + long size = 0; + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + size += getDirectorySize(child); + } else { + size += child.length(); + } + } + } + return size; + } + + private static class TestSection extends PowerStatsSpan.Section { + public static final String TYPE = "much-text"; + + private final int mSize; + private final int mValue; + + TestSection(int value, int size) { + super(TYPE); + mSize = size; + mValue = value; + } + + @Override + void write(TypedXmlSerializer serializer) throws IOException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mSize; i++) { + sb.append("X"); + } + serializer.startTag(null, "much-text"); + serializer.attributeInt(null, "value", mValue); + serializer.text(sb.toString()); + serializer.endTag(null, "much-text"); + } + + public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException { + TestSection section = null; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG + && parser.getName().equals("much-text"))) { + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) { + section = new TestSection(parser.getAttributeInt(null, "value"), 0); + } + } + return section; + } + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } +} 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 2ffe4aacda73..df46054f0f6f 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 49f22eca5643..cf315a4e0b9a 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/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 63281b77ade7..71007f53f0e1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -155,7 +155,7 @@ public class AccessibilityUserStateTest { mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString()); mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); - mUserState.setDisplayMagnificationEnabledLocked(true); + mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); @@ -177,7 +177,7 @@ public class AccessibilityUserStateTest { assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty()); assertNull(mUserState.getTargetAssignedToAccessibilityButton()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); - assertFalse(mUserState.isDisplayMagnificationEnabledLocked()); + assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); 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 1cd61e90126e..efcdbd488a39 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 70527ce2ad32..44d676052352 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/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java index 988cd818ca28..feb6bd930bf3 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertTrue; import android.content.Context; @@ -34,6 +36,7 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -49,8 +52,9 @@ public final class BatteryStatsServiceTest { final Context context = InstrumentationRegistry.getContext(); mBgThread = new HandlerThread("bg thread"); mBgThread.start(); - mBatteryStatsService = new BatteryStatsService(context, - context.getCacheDir(), new Handler(mBgThread.getLooper())); + File systemDir = context.getCacheDir(); + Handler handler = new Handler(mBgThread.getLooper()); + mBatteryStatsService = new BatteryStatsService(context, systemDir, handler); } @After @@ -121,4 +125,14 @@ public final class BatteryStatsServiceTest { waitThread.join(1000); } } + + @Test + public void testSavingStatsdAtomPullTimestamp() { + mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); + assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(1234); + mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); + assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(5478); + } } 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 0230d77e8e14..e3e708ec856d 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 9e5a0479ea70..3a3dd6ea2746 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ComponentName; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.AuthenticateReason; import android.hardware.biometrics.common.ICancellationSignal; @@ -59,6 +60,7 @@ import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.face.UsageStats; import org.junit.Before; @@ -103,7 +105,7 @@ public class FaceAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -112,6 +114,8 @@ public class FaceAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private BiometricManager mBiometricManager; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -236,27 +240,63 @@ public class FaceAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void authWithNoLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_NONE); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mHal).authenticate(OP_ID); + } + + @Test + public void authWithLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_PERMANENT); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt()); + verify(mHal, never()).authenticate(anyInt()); + } + private FaceAuthenticationClient createClient() throws RemoteException { return createClient(2 /* version */, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(2 /* version */, null /* listener */, - true /* allowBackgroundAuthentication */); + true /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); + } + + private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker) + throws RemoteException { + return createClient(0 /* version */, + mClientMonitorCallbackConverter, + true /* allowBackgroundAuthentication */, + lockoutTracker); } private FaceAuthenticationClient createClient(int version, ClientMonitorCallbackConverter listener, - boolean allowBackgroundAuthentication) throws RemoteException { + boolean allowBackgroundAuthentication, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder() .setOpPackageName("test-owner") .setUserId(USER_ID) @@ -270,7 +310,7 @@ public class FaceAuthenticationClientTest { false /* restricted */, options, 4 /* cookie */, false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication, + mUsageStats, lockoutTracker, allowBackgroundAuthentication, null /* sensorPrivacyManager */, 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index ade3e8275157..fbf0e13c2ac9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -87,7 +87,7 @@ public class FaceDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -170,7 +170,7 @@ public class FaceDetectClientTest { private FaceDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceDetectClient(mContext, () -> aidl, mToken, 99 /* requestId */, mClientMonitorCallbackConverter, new FaceAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index 54d116f07805..128f3149e6d4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -83,7 +83,7 @@ public class FaceEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FaceEnrollClientTest { private FaceEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, USER_ID, HAT, "com.foo.bar", 44 /* requestId */, mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java new file mode 100644 index 000000000000..c8bfaa90d863 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceGenerateChallengeClientTest { + private static final String TAG = "FaceGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceGenerateChallengeClient mClient; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + } + + @Test + public void generateChallenge() throws RemoteException { + createClient(mListener); + mClient.start(mCallback); + + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void generateChallenge_nullListener() { + createClient(null); + mClient.start(mCallback); + + verify(mCallback).onClientFinished(mClient, false); + } + + private void createClient(ClientMonitorCallbackConverter listener) { + mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java new file mode 100644 index 000000000000..9d0c84edb366 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +@Presubmit +@SmallTest +public class FaceGetFeatureClientTest { + private static final String TAG = "FaceGetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private FaceGetFeatureClient mClient; + + @Before + public void setUp() throws RemoteException { + mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature); + + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onFeatureGet(true, new byte[]{ + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)}); + return null; + }).when(mSession).getFeatures(); + } + + @Test + public void getFeature() throws RemoteException { + ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class); + mClient.start(mCallback); + + verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(), + featureState.capture()); + assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature)); + assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java new file mode 100644 index 000000000000..1b4c01723027 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.annotation.NonNull; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Presubmit +@SmallTest +public class FaceInternalCleanupClientTest { + private static final String TAG = "FaceInternalCleanupClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private final List<Face> mEnrolledList = new ArrayList<>(); + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalCleanupClient mClient; + private List<Integer> mAddedIds; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + + mEnrolledList.add(mFace); + mAddedIds = new ArrayList<>(); + mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils, + mAuthenticatorIds) { + @Override + protected void onAddUnknownTemplate(int userId, + @NonNull BiometricAuthenticator.Identifier identifier) { + mAddedIds.add(identifier.getBiometricId()); + } + }; + } + + @Test + public void removesUnknownTemplate() throws Exception { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); + } + + assertThat(mAddedIds).isEmpty(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); + + verify(mSession, times(2)).removeEnrollments(captor.capture()); + assertThat(captor.getAllValues().stream() + .flatMap(x -> Arrays.stream(x).boxed()) + .collect(Collectors.toList())) + .containsExactly(1, 2); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception { + mClient.setFavorHalEnrollments(); + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + assertThat(mAddedIds).containsExactly(1, 2); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1), + new Face("three", 3, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + // The first template is removed after enumeration + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); + + // Simulate finishing the removal of the first template. + // |remaining| is 0 because one FaceRemovalClient is associated with only one + // biometrics ID. + mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0); + + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + + // Simulate finishing the removal of the second template. + mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0); + + assertThat(mClient.getUnknownHALTemplates()).isEmpty(); + } + + @Test + public void noUnknownTemplates() throws RemoteException { + mClient.start(mCallback); + mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java new file mode 100644 index 000000000000..8d74fd1d56be --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FaceInternalEnumerateClientTest { + private static final String TAG = "FaceInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + final List<Face> enrolled = new ArrayList<>(); + enrolled.add(mFace); + mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, + TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void internalCleanupClient_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(mFace, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_templatesRemaining() throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_differentIdentifier_noTemplatesRemaining() + throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java index 76a5accc5fe9..1d9e9334f043 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java @@ -76,7 +76,7 @@ public class FaceRemovalClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private BiometricUtils<Face> mUtils; @Mock @@ -115,7 +115,7 @@ public class FaceRemovalClientTest { private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceRemovalClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, biometricIds, USER_ID, "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java new file mode 100644 index 000000000000..dbbd69bdd3b5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceResetLockoutClientTest { + private static final String TAG = "FaceResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + private final byte[] mHardwareAuthToken = new byte[69]; + @Mock + private LockoutCache mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FaceResetLockoutClient mClient; + + @Before + public void setUp() { + mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID, + mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, BIOMETRIC_STRONG); + + when(mAidlSession.getSession()).thenReturn(mSession); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + } + + @Test + public void testResetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void testResetLockout_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID, + BIOMETRIC_STRONG, -1); + verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID, + LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java new file mode 100644 index 000000000000..fb5502a1795d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceRevokeChallengeClientTest { + private static final String TAG = "FaceRevokeChallengeClientTest"; + private static final long CHALLENGE = 200L; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java new file mode 100644 index 000000000000..eb8cc9c63e75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceSetFeatureClientTest { + private static final String TAG = "FaceSetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private final boolean mEnabled = true; + private final byte[] mHardwareAuthToken = new byte[69]; + private FaceSetFeatureClient mClient; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID, + TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled, + mHardwareAuthToken); + } + + @Test + public void setFeature_onFeatureSet() throws RemoteException { + doAnswer(invocation -> { + mClient.onFeatureSet(true); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(true, mFeature); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void setFeature_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(false, mFeature); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index be9f52e00b16..7a293e80c183 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -74,7 +74,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -94,7 +94,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -111,10 +111,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test @@ -153,11 +152,11 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, - internalProp, mLockoutResetDispatcher, mBiometricContext); + final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, + null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext); mScheduler.reset(); @@ -181,7 +180,7 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..9a40e8a7201a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC; +import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; +import android.hardware.biometrics.face.Feature; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.OptionalUint64; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFace mSession; + @Mock + FaceManager mFaceManager; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + @Mock + private Clock mClock; + + private final long mChallenge = 100L; + private AidlToHidlAdapter mAidlToHidlAdapter; + private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */); + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY; + private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION}; + + @Before + public void setUp() throws RemoteException { + TestableContext testableContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + testableContext.addMockSystemService(FaceManager.class, mFaceManager); + mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */, + mAidlResponseHandler, mClock); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = mChallenge; + + when(mSession.generateChallenge(anyInt())).thenReturn(result); + when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace)); + } + + @Test + public void testGenerateChallengeCache() throws RemoteException { + verify(mSession).setCallback(any()); + + final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class); + + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC); + verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture()); + assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge); + + forwardTime(10 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(20 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //Confirms that the challenge is cached and the hal method is not called again + verifyNoMoreInteractions(mSession); + verify(mAidlResponseHandler, times(3)) + .onChallengeGenerated(mChallenge); + + forwardTime(60 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //HAL method called after challenge has timed out + verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC); + } + + @Test + public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException { + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.generateChallenge(); + forwardTime(10 /* seconds */); + } + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.revokeChallenge(0); + forwardTime((i + 1) * 10 /* seconds */); + } + + verify(mSession).revokeChallenge(); + } + + @Test + public void testRevokeChallenge_timeout() throws RemoteException { + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(700); + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.revokeChallenge(0); + + verify(mSession).revokeChallenge(); + } + + @Test + public void testEnroll() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken, + EnrollmentType.DEFAULT, mFeatures, + null /* previewSurface */); + ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture()); + + ArrayList<Integer> features = featureCaptor.getValue(); + + assertThat(features).containsExactly( + AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0])); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollments = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollments); + + verify(mSession).remove(enrollments[0]); + } + + @Test + public void testGetFeatures_onResultSuccess() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = true; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue()[0]).isEqualTo( + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)); + } + + @Test + public void testGetFeatures_onResultFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = false; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue().length).isEqualTo(0); + } + + @Test + public void testGetFeatures_onStatusFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.INTERNAL_ERROR; + result.value = false; + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0); + } + + @Test + public void testGetFeatures_featureNotSet() throws RemoteException { + mAidlToHidlAdapter.getFeatures(); + + verify(mSession, never()).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + } + + @Test + public void testSetFeatureSuccessful() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onFeatureSet(feature); + } + + @Test + public void testSetFeatureFailed() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())) + .thenReturn(Status.INTERNAL_ERROR); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, + 0 /* vendorCode */); + } + + @Test + public void testGetAuthenticatorId() throws RemoteException { + final long authenticatorId = 2L; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = authenticatorId; + + when(mSession.getAuthenticatorId()).thenReturn(result); + + mAidlToHidlAdapter.getAuthenticatorId(); + + verify(mSession).getAuthenticatorId(); + verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).resetLockout(hatCaptor.capture()); + + assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken)); + } + + private ArrayList<Byte> processHAT(HardwareAuthToken hat) { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + return hardwareAuthToken; + } + + private void forwardTime(long seconds) { + when(mClock.millis()).thenReturn(seconds * 1000); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 8a11e31014d5..79a528c59f49 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -53,11 +53,15 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -66,6 +70,7 @@ import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; import org.junit.Rule; @@ -102,6 +107,9 @@ public class FingerprintAuthenticationClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private ISession mHal; @@ -124,7 +132,7 @@ public class FingerprintAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -135,6 +143,8 @@ public class FingerprintAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private Clock mClock; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -425,37 +435,65 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void testLockoutTracker_authSuccess() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testLockoutTracker_authFailed() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt()); + verify(mLockoutTracker).addFailedAttemptForUser(USER_ID); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithoutBackgroundAuth() throws RemoteException { return createClient(100 /* version */, false /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - null /* listener */); + null, /* listener */null); } private FingerprintAuthenticationClient createClient(int version, - boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener) + boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder() .setOpPackageName("test-owner") - .setUserId(5) - .setSensorId(9) + .setUserId(USER_ID) + .setSensorId(SENSOR_ID) .build(); return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, REQUEST_ID, listener, OP_ID, @@ -463,10 +501,11 @@ public class FingerprintAuthenticationClientTest { false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - null /* taskStackListener */, null /* lockoutCache */, + null /* taskStackListener */, mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, - new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) { + new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, + lockoutTracker) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 78d3a9dd9f9e..a467c84845cd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -83,7 +83,7 @@ public class FingerprintDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FingerprintDetectClientTest { @Test public void testWhenListenerIsNull() { - final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, null /* listener */, new FingerprintAuthenticateOptions.Builder() @@ -173,7 +173,7 @@ public class FingerprintDetectClientTest { private FingerprintDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, mClientMonitorCallbackConverter, new FingerprintAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index ef253801b118..c7eb1db3e74b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -102,7 +102,7 @@ public class FingerprintEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private Probe mLuxProbe; @Captor @@ -291,7 +291,7 @@ public class FingerprintEnrollClientTest { private FingerprintEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID, mClientMonitorCallbackConverter, 0 /* userId */, HAT, "owner", mBiometricUtils, 8 /* sensorId */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java new file mode 100644 index 000000000000..840961938cb2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintGenerateChallengeClientTest { + private static final String TAG = "FingerprintGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintGenerateChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken, + mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void generateChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + mClient.start(mCallback); + + verify(mSession).generateChallenge(); + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index 580644347c84..c9482ceb00f5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.Fingerprint; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; @@ -41,7 +42,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -70,9 +70,9 @@ public class FingerprintInternalCleanupClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Mock - private AidlSession mAidlSession; + ISession mSession; @Mock - private ISession mSession; + private AidlSession mAidlSession; @Mock private BiometricLogger mLogger; @Mock @@ -87,15 +87,16 @@ public class FingerprintInternalCleanupClientTest { @Before public void setup() { - when(mAidlSession.getSession()).thenReturn(mSession); mAddedIds = new ArrayList<>(); + + when(mAidlSession.getSession()).thenReturn(mSession); } - @Ignore("TODO(b/229015801): verify cleanup behavior") @Test public void removesUnknownTemplate() throws Exception { mClient = createClient(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); final List<Fingerprint> templates = List.of( new Fingerprint("one", 1, 1), new Fingerprint("two", 2, 1) @@ -108,8 +109,8 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).isEmpty(); - final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); verify(mSession, times(2)).removeEnrollments(captor.capture()); assertThat(captor.getAllValues().stream() .flatMap(x -> Arrays.stream(x).boxed()) @@ -132,13 +133,15 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).containsExactly(1, 2); verify(mSession, never()).removeEnrollments(any()); verify(mCallback).onClientFinished(eq(mClient), eq(true)); } @Test - public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() + throws RemoteException { mClient = createClient(); final List<Fingerprint> templates = List.of( @@ -150,6 +153,8 @@ public class FingerprintInternalCleanupClientTest { for (int i = templates.size() - 1; i >= 0; i--) { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + + verify(mSession).enumerateEnrollments(); // The first template is removed after enumeration assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java new file mode 100644 index 000000000000..723f916f99c8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Presubmit +@SmallTest +public class FingerprintInternalEnumerateClientTest { + private static final String TAG = "FingerprintInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + + private FingerprintInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + List<Fingerprint> enrolled = new ArrayList<>(); + enrolled.add(new Fingerprint("one", 1, 1)); + mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, + mBiometricContext); + } + + @Test + public void internalEnumerate_unknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1); + mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().stream() + .flatMap(x -> Stream.of(x.getBiometricId())) + .collect(Collectors.toList())).containsExactly(2, 3); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalEnumerate_noUnknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java new file mode 100644 index 000000000000..64f07e20e958 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Presubmit +@SmallTest +public class FingerprintRemovalClientTest { + private static final String TAG = "FingerprintRemovalClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private FingerprintRemovalClient mClient; + private int[] mBiometricIds = new int[]{1, 2}; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener, + mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID, + mBiometricLogger, mBiometricContext, mAuthenticatorIds); + } + + @Test + public void removalMultipleFingerprints() throws RemoteException { + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn( + List.of(new Fingerprint("three", 3, 1))); + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verifyNoMoreInteractions(mAuthenticatorIds); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void removeFingerprint_nullIdentifier() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(null, 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, false); + } + + @Test + public void removeFingerprints_noFingerprintEnrolled() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>()); + + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verify(mAuthenticatorIds).put(USER_ID, 0L); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java new file mode 100644 index 000000000000..a4746dea7ac5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintResetLockoutClientTest { + private static final String TAG = "FingerprintResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private LockoutTracker mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FingerprintResetLockoutClient mClient; + + @Before + public void setUp() { + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69], + mLockoutTracker, mLockoutResetDispatcher, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void resetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), + eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java new file mode 100644 index 000000000000..f19b2f7da8a5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintRevokeChallengeClientTest { + private static final String TAG = "FingerprintRevokeChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge_sameChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void revokeChallenge_differentChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE + 1); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 15d7601dde34..410260074fbd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -75,7 +75,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -97,7 +97,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -113,10 +113,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..b78ba82bd8fe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFingerprint mSession; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + + private final long mChallenge = 100L; + private final int mUserId = 0; + private AidlToHidlAdapter mAidlToHidlAdapter; + + @Before + public void setUp() { + mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId, + mAidlResponseHandler); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + } + + @Test + public void testGenerateChallenge() throws RemoteException { + when(mSession.preEnroll()).thenReturn(mChallenge); + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).preEnroll(); + verify(mAidlResponseHandler).onChallengeGenerated(mChallenge); + } + + @Test + public void testRevokeChallenge() throws RemoteException { + mAidlToHidlAdapter.revokeChallenge(mChallenge); + + verify(mSession).postEnroll(); + verify(mAidlResponseHandler).onChallengeRevoked(0L); + } + + @Test + public void testEnroll() throws RemoteException { + final ICancellationSignal cancellationSignal = + mAidlToHidlAdapter.enroll(mHardwareAuthToken); + + verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC)); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0 /* operationId */, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollmentIds = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, enrollmentIds[0]); + } + + @Test + public void testRemoveMultipleEnrollments() throws RemoteException { + final int[] enrollmentIds = new int[]{1, 2}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, 0); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + verify(mAidlResponseHandler).onLockoutCleared(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e8cbcf9a6874..a3d415e4918f 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -337,11 +337,7 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); - mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); - mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); - mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM); + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); 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 e7777f75b6df..5cc84b197e03 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,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Collections; +import java.util.List; + /** * Test for {@link ContentCaptureManagerService}. * @@ -84,6 +90,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; @@ -96,6 +104,10 @@ public class ContentCaptureManagerServiceTest { private boolean mDevCfgEnableContentProtectionReceiver; + private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a")); + + private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + private int mContentProtectionBlocklistManagersCreated; private int mContentProtectionServiceInfosCreated; @@ -367,7 +379,21 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_withoutManagers() { + public void isContentProtectionReceiverEnabled_true() { + when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true); + when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true); + mDevCfgEnableContentProtectionReceiver = true; + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isTrue(); + } + + @Test + public void isContentProtectionReceiverEnabled_false_withoutManagers() { boolean actual = mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( USER_ID, PACKAGE_NAME); @@ -378,7 +404,7 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_disabledWithFlag() { + public void isContentProtectionReceiverEnabled_false_disabledWithFlag() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false; @@ -393,6 +419,22 @@ public class ContentCaptureManagerServiceTest { } @Test + public void isContentProtectionReceiverEnabled_false_emptyGroups() { + mDevCfgEnableContentProtectionReceiver = true; + mDevCfgContentProtectionRequiredGroups = Collections.emptyList(); + mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt()); + verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString()); + } + + @Test public void onLoginDetected_disabledAfterConstructor() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); @@ -436,12 +478,92 @@ public class ContentCaptureManagerServiceTest { verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS); } + @Test + 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_disabled_empty() { + mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); + } + + @Test + 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() { super(sContext); this.mDevCfgEnableContentProtectionReceiver = ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver; + this.mDevCfgContentProtectionRequiredGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups; + this.mDevCfgContentProtectionOptionalGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups; } @Override 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 9b32a809d2b5..de3cfbf859ff 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 16fdfb16444a..76aa40c04e3d 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 a029db922372..fa3c7a4c4769 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 23f14f8468bb..02b86db6ab6f 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/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java index 75d71daa208d..06f117bdbd65 100644 --- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java @@ -38,9 +38,10 @@ import org.junit.runners.JUnit4; public class BluetoothRouteControllerTest { private final BluetoothRouteController.BluetoothRoutesUpdatedListener - mBluetoothRoutesUpdatedListener = routes -> { - // Empty on purpose. - }; + mBluetoothRoutesUpdatedListener = + () -> { + // Empty on purpose. + }; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java index ec4b8a804533..14b121d3945c 100644 --- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -38,7 +38,7 @@ import org.junit.runners.JUnit4; public class DeviceRouteControllerTest { private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener = - deviceRoute -> { + () -> { // Empty on purpose. }; 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 d85768dd7588..f94aff706a67 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; @@ -734,6 +746,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 { mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java new file mode 100644 index 000000000000..07cdf4df47ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for the {@link MediaProjectionSessionIdGenerator} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionSessionIdGeneratorTest { + + private static final String TEST_PREFS_FILE = "media-projection-session-id-test"; + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); + private final SharedPreferences mSharedPreferences = createSharePreferences(); + private final MediaProjectionSessionIdGenerator mGenerator = + createGenerator(mSharedPreferences); + + @Before + public void setUp() { + mSharedPreferences.edit().clear().commit(); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + mSharedPreferencesFile.delete(); + } + + @Test + public void getCurrentSessionId_byDefault_returns0() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void getCurrentSessionId_multipleTimes_returnsSameValue() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void createAndGetNewSessionId_returnsIncrementedId() { + int previousValue = mGenerator.getCurrentSessionId(); + + int newValue = mGenerator.createAndGetNewSessionId(); + + assertThat(newValue).isEqualTo(previousValue + 1); + } + + @Test + public void createAndGetNewSessionId_persistsNewValue() { + int newValue = mGenerator.createAndGetNewSessionId(); + + MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences()); + + assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue); + } + + private SharedPreferences createSharePreferences() { + return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); + } + + private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) { + return new MediaProjectionSessionIdGenerator(sharedPreferences); + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java new file mode 100644 index 000000000000..7723541dc160 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java @@ -0,0 +1,104 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; + +/** + * Tests for the {@link MediaProjectionTimestampStore} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionTimestampStoreTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionTimestampStoreTest { + + private static final String TEST_PREFS_FILE = "media-projection-timestamp-test"; + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); + private final SharedPreferences mSharedPreferences = createSharePreferences(); + + private Instant mCurrentInstant = Instant.ofEpochMilli(0); + + private final InstantSource mInstantSource = () -> mCurrentInstant; + private final MediaProjectionTimestampStore mStore = + new MediaProjectionTimestampStore(mSharedPreferences, mInstantSource); + + @Before + public void setUp() { + mSharedPreferences.edit().clear().commit(); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + mSharedPreferencesFile.delete(); + } + + @Test + public void timeSinceLastActiveSession_byDefault_returnsNull() { + assertThat(mStore.timeSinceLastActiveSession()).isNull(); + } + + @Test + public void timeSinceLastActiveSession_returnsBasedOnLastActiveSessionEnded() { + mCurrentInstant = Instant.ofEpochMilli(0); + mStore.registerActiveSessionEnded(); + + mCurrentInstant = mCurrentInstant.plusSeconds(60); + + assertThat(mStore.timeSinceLastActiveSession()).isEqualTo(Duration.ofSeconds(60)); + } + + @Test + public void timeSinceLastActiveSession_valueIsPersisted() { + mCurrentInstant = Instant.ofEpochMilli(0); + mStore.registerActiveSessionEnded(); + + MediaProjectionTimestampStore newStoreInstance = + new MediaProjectionTimestampStore(createSharePreferences(), mInstantSource); + mCurrentInstant = mCurrentInstant.plusSeconds(123); + + assertThat(newStoreInstance.timeSinceLastActiveSession()) + .isEqualTo(Duration.ofSeconds(123)); + } + + private SharedPreferences createSharePreferences() { + return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS new file mode 100644 index 000000000000..6dfb888dedad --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pdb/OWNERS diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp index ca5cfa5b60f5..6f37967bf7f0 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", @@ -34,6 +35,7 @@ android_test { "platform-test-annotations", "service-permission.stubs.system_server", "services.core", + "flag-junit", ], platform_apis: true, 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 bc826a3cf4a6..04158c4d4f93 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 @@ -87,6 +92,21 @@ public class HapticFeedbackCustomizationTest { } @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>" + "<constant id=\"10\">" diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 000355598281..3d0dca0de87e 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -16,20 +16,24 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; +import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; +import static android.os.VibrationEffect.EFFECT_CLICK; import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK; import static android.os.VibrationEffect.EFFECT_TICK; import static android.view.HapticFeedbackConstants.CLOCK_TICK; import static android.view.HapticFeedbackConstants.CONTEXT_CLICK; +import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE; +import static android.view.HapticFeedbackConstants.KEYBOARD_TAP; import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED; -import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE; import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS; import static android.view.HapticFeedbackConstants.SCROLL_LIMIT; import static android.view.HapticFeedbackConstants.SCROLL_TICK; - +import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -42,9 +46,10 @@ import android.hardware.vibrator.IVibrator; import android.os.VibrationAttributes; 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; -import android.view.flags.FeatureFlags; import androidx.test.InstrumentationRegistry; @@ -62,6 +67,8 @@ import java.io.FileOutputStream; public class HapticFeedbackVibrationProviderTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final VibrationEffect PRIMITIVE_TICK_EFFECT = VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose(); private static final VibrationEffect PRIMITIVE_CLICK_EFFECT = @@ -69,11 +76,15 @@ public class HapticFeedbackVibrationProviderTest { private static final int[] SCROLL_FEEDBACK_CONSTANTS = new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}; + private static final int[] KEYBOARD_FEEDBACK_CONSTANTS = + new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE}; + + private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f; + private Context mContext = InstrumentationRegistry.getContext(); private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO; @Mock private Resources mResourcesMock; - @Mock private FeatureFlags mViewFeatureFlags; @Test public void testNonExistentCustomization_useDefault() throws Exception { @@ -214,6 +225,62 @@ public class HapticFeedbackVibrationProviderTest { } @Test + public void testKeyboardHaptic_noFixedAmplitude_defaultVibrationReturned() { + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + SparseArray<VibrationEffect> customizations = new SparseArray<>(); + customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT); + customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT); + + // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`. + HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(PRIMITIVE_CLICK_EFFECT); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(PRIMITIVE_TICK_EFFECT); + + // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`. + hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */)); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */)); + } + + @Test + public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */)); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */)); + } + + @Test + public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE) + .compose()); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE) + .compose()); + } + + @Test public void testVibrationAttribute_forNotBypassingIntensitySettings() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); @@ -235,7 +302,7 @@ public class HapticFeedbackVibrationProviderTest { @Test public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() { - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true); + mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { @@ -248,7 +315,7 @@ public class HapticFeedbackVibrationProviderTest { @Test public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() { - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false); + mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { @@ -259,14 +326,71 @@ public class HapticFeedbackVibrationProviderTest { } } + @Test + public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId) + .that(attrs.getCategory()).isEqualTo(0); + } + } + + @Test + public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) + .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD); + } + } + + @Test + public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(-1); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + + effectId) + .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse(); + } + } + + @Test + public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + + effectId) + .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue(); + } + } + private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() { return createProvider(/* customizations= */ null); } private HapticFeedbackVibrationProvider createProvider( SparseArray<VibrationEffect> customizations) { - return new HapticFeedbackVibrationProvider( - mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags); + return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); } private void mockVibratorPrimitiveSupport(int... supportedPrimitives) { @@ -287,6 +411,11 @@ public class HapticFeedbackVibrationProviderTest { .thenReturn(vibrationPattern); } + private void mockKeyboardVibrationFixedAmplitude(float amplitude) { + when(mResourcesMock.getFloat(R.dimen.config_keyboardHapticFeedbackFixedAmplitude)) + .thenReturn(amplitude); + } + private void setupCustomizationFile(String xml) throws Exception { File file = new File(mContext.getCacheDir(), "test.xml"); file.createNewFile(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 1ae096617dce..7a2bb5a90846 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -69,7 +69,11 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.test.TestLooper; +import android.os.vibrator.Flags; import android.os.vibrator.VibrationConfig; +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.util.ArraySet; import android.view.Display; @@ -95,6 +99,9 @@ import java.util.Set; public class VibrationSettingsTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int UID = 1; private static final int VIRTUAL_DISPLAY_ID = 1; private static final String SYSUI_PACKAGE_NAME = "sysui"; @@ -606,6 +613,47 @@ public class VibrationSettingsTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED) + public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() { + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0); + + // Keyboard touch ignored. + assertVibrationIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build(), + Vibration.Status.IGNORED_FOR_SETTINGS); + + // General touch and keyboard touch with bypass flag not ignored. + assertVibrationNotIgnoredForUsage(USAGE_TOUCH); + assertVibrationNotIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .build()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED) + public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() { + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1); + + // General touch ignored. + assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); + + // Keyboard touch not ignored. + assertVibrationNotIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build()); + } + + @Test public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() { // Vibrations from the primary display is never ignored regardless of the creation and // removal of virtual displays and of the changes of apps running on virtual displays. @@ -895,6 +943,14 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(callerInfo)); } + private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs, + Vibration.Status expectedStatus) { + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, + Display.DEFAULT_DISPLAY, null, null); + assertEquals(errorMessageForAttributes(attrs), expectedStatus, + mVibrationSettings.shouldIgnoreVibration(callerInfo)); + } + private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) { assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0); } @@ -919,10 +975,21 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(callerInfo)); } + private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) { + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, + Display.DEFAULT_DISPLAY, null, null); + assertNull(errorMessageForAttributes(attrs), + mVibrationSettings.shouldIgnoreVibration(callerInfo)); + } + private String errorMessageForUsage(int usage) { return "Error for usage " + VibrationAttributes.usageToString(usage); } + private String errorMessageForAttributes(VibrationAttributes attrs) { + return "Error for attributes " + attrs; + } + private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) { when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 40e0e84dca59..3dfaed69dea6 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -82,6 +82,7 @@ import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.ArraySet; import android.util.SparseArray; @@ -89,7 +90,7 @@ import android.util.SparseBooleanArray; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.InputDevice; -import android.view.flags.FeatureFlags; +import android.view.flags.Flags; import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; @@ -155,6 +156,8 @@ public class VibratorManagerServiceTest { @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock; @Mock @@ -175,8 +178,6 @@ public class VibratorManagerServiceTest { private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock; @Mock private AudioManager mAudioManagerMock; - @Mock - private FeatureFlags mViewFeatureFlags; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); @@ -326,8 +327,7 @@ public class VibratorManagerServiceTest { HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( Resources resources, VibratorInfo vibratorInfo) { return new HapticFeedbackVibrationProvider( - resources, vibratorInfo, mHapticFeedbackVibrationMap, - mViewFeatureFlags); + resources, vibratorInfo, mHapticFeedbackVibrationMap); } }); return mService; @@ -1354,7 +1354,7 @@ public class VibratorManagerServiceTest { denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE); denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING); // Flag override to enable the scroll feedack constants to bypass interruption policies. - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); mHapticFeedbackVibrationMap.put( HapticFeedbackConstants.SCROLL_TICK, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 8e7ba7030e82..dd7dec0bdb2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -130,12 +130,22 @@ public class BackNavigationControllerTests extends WindowTestsBase { // verify if back animation would start. assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); - // reset drawing status + // reset drawing status to test translucent activity backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - topTask.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + final ActivityRecord topActivity = topTask.getTopMostActivity(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); + // simulate translucent + topActivity.setOccludesParent(false); + backNavigationInfo = startBackNavigation(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + + // reset drawing status to test keyguard occludes + topActivity.setOccludesParent(true); + backNavigationInfo.onBackNavigationFinished(false); + mBackNavigationController.clearBackAnimations(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -201,9 +211,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - testCase.recordFront.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 233a2076a867..6a738befba9a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -18,16 +18,20 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.utils.LastCallVerifier.lastCall; import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; 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.reset; import static org.mockito.Mockito.when; import android.graphics.Rect; @@ -35,8 +39,9 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.SurfaceSession; -import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.testutils.StubTransaction; +import com.android.server.wm.utils.MockAnimationAdapter; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -118,102 +123,147 @@ public class DimmerTests extends WindowTestsBase { } } - private MockSurfaceBuildingContainer mHost; - private Dimmer mDimmer; - private SurfaceControl.Transaction mTransaction; - private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter; + static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory { + public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, + SurfaceAnimationRunner runner) { + return sTestAnimation; + } + } - private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { + private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter { @Override public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type) { + AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) { surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim); } } + private MockSurfaceBuildingContainer mHost; + private Dimmer mDimmer; + private SurfaceControl.Transaction mTransaction; + private TestWindowContainer mChild; + private static AnimationAdapter sTestAnimation; + private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter; + @Before public void setUp() throws Exception { mHost = new MockSurfaceBuildingContainer(mWm); - mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); mTransaction = spy(StubTransaction.class); - mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter); + mChild = new TestWindowContainer(mWm); + if (Flags.dimmerRefactor()) { + sTestAnimation = spy(new MockAnimationAdapter()); + mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory()); + } else { + sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); + mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter); + } } @Test public void testUpdateDimsAppliesCrop() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + mHost.addChild(mChild, 0); - final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); + mDimmer.adjustAppearance(mChild, 1, 1); + mDimmer.adjustRelativeLayer(mChild, -1); int width = 100; int height = 300; - mDimmer.mDimState.mDimBounds.set(0, 0, width, height); + mDimmer.getDimBounds().set(0, 0, width, height); mDimmer.updateDims(mTransaction); - verify(mTransaction).setWindowCrop(getDimLayer(), width, height); - verify(mTransaction).show(getDimLayer()); + verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height); + verify(mTransaction).show(mDimmer.getDimLayer()); } @Test - public void testDimAboveWithChildCreatesSurfaceAboveChild() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + final float alpha = 0.7f; + final int blur = 50; + mHost.addChild(mChild, 0); + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); - verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1); + mDimmer.updateDims(mTransaction); + verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1); + verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); + verify(mTransaction).setBackgroundBlurRadius(dimLayer, blur); } @Test - public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimBelow(child, alpha, 0); - SurfaceControl dimLayer = getDimLayer(); + public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + final float alpha = 0.7f; + mHost.addChild(mChild, 0); + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1); } @Test - public void testDimBelowWithChildSurfaceDestroyedWhenReset() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + final int blur = 50; + // Dim once + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + // Reset, and don't dim + mDimmer.resetDimStates(); + mDimmer.adjustRelativeLayer(mChild, -1); + mDimmer.updateDims(mTransaction); + verify(mTransaction).show(dimLayer); + verify(mTransaction).remove(dimLayer); + } + + @Test + public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + + final float alpha = 0.8f; + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.resetDimStates(); mDimmer.updateDims(mTransaction); - verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), + verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), + any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), + anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); verify(mHost.getPendingTransaction()).remove(dimLayer); } @Test public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + final int blur = 20; + // Dim once + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + // Reset and dim again mDimmer.resetDimStates(); - mDimmer.dimAbove(child, alpha); - + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); verify(mTransaction, never()).remove(dimLayer); @@ -221,14 +271,13 @@ public class DimmerTests extends WindowTestsBase { @Test public void testDimUpdateWhileDimming() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); - + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - final Rect bounds = mDimmer.mDimState.mDimBounds; + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); + final Rect bounds = mDimmer.getDimBounds(); - SurfaceControl dimLayer = getDimLayer(); + SurfaceControl dimLayer = mDimmer.getDimLayer(); bounds.set(0, 0, 10, 10); mDimmer.updateDims(mTransaction); verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height()); @@ -242,41 +291,58 @@ public class DimmerTests extends WindowTestsBase { } @Test - public void testRemoveDimImmediately() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testRemoveDimImmediately_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + mDimmer.adjustAppearance(mChild, 1, 2); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + verify(mTransaction, times(1)).show(dimLayer); + + reset(sTestAnimation); + mDimmer.dontAnimateExit(); + mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction); + verify(sTestAnimation, never()).startAnimation( + any(SurfaceControl.class), any(SurfaceControl.Transaction.class), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).remove(dimLayer); + } - mDimmer.dimAbove(child, 1); - SurfaceControl dimLayer = getDimLayer(); + @Test + public void testRemoveDimImmediately_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + mDimmer.adjustAppearance(mChild, 1, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); - reset(mSurfaceAnimatorStarter); + reset(sSurfaceAnimatorStarter); mDimmer.dontAnimateExit(); mDimmer.resetDimStates(); mDimmer.updateDims(mTransaction); - verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), + verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), + any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); verify(mTransaction).remove(dimLayer); } @Test - public void testDimmerWithBlurUpdatesTransaction() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testDimmerWithBlurUpdatesTransaction_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); final int blurRadius = 50; - mDimmer.dimBelow(child, 0, blurRadius); - SurfaceControl dimLayer = getDimLayer(); + mDimmer.adjustAppearance(mChild, 1, blurRadius); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); - } - - private SurfaceControl getDimLayer() { - return mDimmer.mDimState.mDimLayer; + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1); } } 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 6c48a6961bc2..9f43a1785266 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -94,6 +94,16 @@ public class SafeActivityOptionsTest { } @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 MockitoSession mockingSession = mockitoSession() diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 2bf13857e537..6235b3b67145 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,7 +26,9 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; @@ -1655,6 +1657,127 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(frontMostTaskFragment, tf0); } + @Test + public void testApplyTransaction_reorderToBottomOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to bottom + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf0, task.getChildAt(2).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord()); + assertEquals(tf1, task.getChildAt(0).asTaskFragment()); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to top + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_TOP_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(tf0, task.getChildAt(3).asTaskFragment()); + assertEquals(topActivity, task.getChildAt(2).asActivityRecord()); + assertEquals(tf1, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + + @Test + public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_TOP_OF_TASK); + } + + private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + @TaskFragmentOperation.OperationType int opType) { + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Apply reorder transaction, which is expected to fail for non-system organizer. + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + opType).build(); + mTransaction + .addTaskFragmentOperation(tf0.getFragmentToken(), operation) + .setErrorCallbackToken(mErrorToken); + assertApplyTransactionAllowed(mTransaction); + // The pending event will be dispatched on the handler (from requestTraversal). + waitHandlerIdle(mWm.mAnimationHandler); + + assertTaskFragmentErrorTransaction(opType, SecurityException.class); + + // Ensure no change to the order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the @@ -1782,6 +1905,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activityToken, change.getActivityToken()); } + /** Setups an embedded TaskFragment. */ + private TaskFragment createTaskFragment(Task task) { + final IBinder token = new Binder(); + TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment); + return taskFragment; + } + /** Setups an embedded TaskFragment in a PIP Task. */ private void setupTaskFragmentInPip() { mTaskFragment = new TaskFragmentBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 5205bb0038c0..7822071f3933 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -37,6 +37,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK; +import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -687,4 +689,24 @@ public class TaskFragmentTest extends WindowTestsBase { tf0.setIsolatedNav(true); assertTrue(tf0.isIsolatedNav()); } + + @Test + public void testGetDimBounds() { + final Task task = mTaskFragment.getTask(); + final Rect taskBounds = task.getBounds(); + mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10, + taskBounds.top + 10); + final Rect taskFragmentBounds = mTaskFragment.getBounds(); + + // Return Task bounds if dimming on parent Task. + final Rect dimBounds = new Rect(); + mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK); + mTaskFragment.getDimBounds(dimBounds); + assertEquals(taskBounds, dimBounds); + + // Return TF bounds by default. + mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT); + mTaskFragment.getDimBounds(dimBounds); + assertEquals(taskFragmentBounds, dimBounds); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index e86fc366a631..eaeb8049b81a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -100,6 +100,9 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerService.WindowContainerInfo; + +import com.google.common.truth.Expect; import org.junit.Rule; import org.junit.Test; @@ -125,6 +128,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { InstrumentationRegistry.getInstrumentation().getUiAutomation(), ADD_TRUSTED_DISPLAY); + @Rule + public Expect mExpect = Expect.create(); + @Test public void testIsRequestedOrientationMapped() { mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true, @@ -674,64 +680,68 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() { - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() { Binder cookie = new Binder("test cookie"); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); final ActivityRecord testActivity = new ActivityBuilder(mAtm) .setCreateTask(true) .build(); - wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() { final Binder cookie = new Binder("ginger cookie"); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie, launchRootTask); + final int uid = 123; + setupActivityWithLaunchCookie(cookie, launchRootTask, uid); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isEqualTo(launchRootTask); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask); + mExpect.that(wci.getUid()).isEqualTo(uid); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() { final Binder cookie1 = new Binder("ginger cookie"); final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie1, launchRootTask1); + final int uid1 = 123; + setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1); - assertThat(wct).isEqualTo(launchRootTask1); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask1); + mExpect.that(wci.getUid()).isEqualTo(uid1); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() { setupActivityWithLaunchCookie(new Binder("ginger cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 123); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie( + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie( new Binder("some other cookie")); - assertThat(wct).isNull(); + assertThat(wci).isNull(); } @Test @@ -778,17 +788,18 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); Task task = createTask(mDefaultDisplay); ActivityRecord activityRecord = createActivityRecord(task); - ContentRecordingSession session = ContentRecordingSession.createTaskSession( - activityRecord.mLaunchCookie); + ContentRecordingSession session = + ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie); wmInternal.setContentRecordingSession(session); - assertThat(session.getTokenToRecord()).isEqualTo( - task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTokenToRecord()) + .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid()); } @Test @@ -1010,12 +1021,12 @@ public class WindowManagerServiceTests extends WindowTestsBase { } } - private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { + private void setupActivityWithLaunchCookie( + IBinder launchCookie, WindowContainerToken wct, int uid) { final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); when(remoteToken.toWindowContainerToken()).thenReturn(wct); - final ActivityRecord testActivity = new ActivityBuilder(mAtm) - .setCreateTask(true) - .build(); + final ActivityRecord testActivity = + new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build(); testActivity.mLaunchCookie = launchCookie; testActivity.getTask().mRemoteToken = remoteToken; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7168670f9652..0b77fd828745 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -40,6 +40,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -58,6 +59,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; @@ -77,11 +79,13 @@ import android.util.Rational; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowInsets; +import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; +import android.window.TaskFragmentOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -579,6 +583,87 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testTaskFragmentHiddenAndFocusableChanges() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, true /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + // Should be visible and focusable initially. + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment hidden and not focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be not visible and not focusable after the transaction. + assertFalse(taskFragment.shouldBeVisible(null)); + assertFalse(taskFragment.isFocusable()); + assertFalse(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment not hidden and focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be visible and focusable after the transaction. + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + } + + @Test + public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + + // Non-system organizers are not allow to update the hidden and focusable states. + assertThrows(SecurityException.class, () -> + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */) + ); + } + + @Test public void testContainerTranslucentChanges() { removeGlobalMinSizeRestriction(); final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) @@ -1600,4 +1685,20 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskIds.contains(expectedTasks[i].mTaskId)); } } + + @NonNull + private TaskFragmentOrganizer createTaskFragmentOrganizer( + @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer organizerInterface = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController + .registerOrganizerInternal( + ITaskFragmentOrganizer.Stub.asInterface( + organizer.getOrganizerToken().asBinder()), + isSystemOrganizer); + t.setTaskFragmentOrganizer(organizerInterface); + + return organizer; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java new file mode 100644 index 000000000000..320d09444681 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import org.mockito.internal.verification.VerificationModeFactory; +import org.mockito.internal.verification.api.VerificationData; +import org.mockito.invocation.Invocation; +import org.mockito.invocation.MatchableInvocation; +import org.mockito.verification.VerificationMode; + +import java.util.List; + +/** + * Verifier to check that the last call of a method received the expected argument + */ +public class LastCallVerifier implements VerificationMode { + + /** + * Allows comparing the expected invocation with the last invocation on the same method + */ + public static LastCallVerifier lastCall() { + return new LastCallVerifier(); + } + + @Override + public void verify(VerificationData data) { + List<Invocation> invocations = data.getAllInvocations(); + MatchableInvocation target = data.getTarget(); + for (int i = invocations.size() - 1; i >= 0; i--) { + final Invocation invocation = invocations.get(i); + if (target.hasSameMethod(invocation)) { + if (target.matches(invocation)) { + return; + } else { + throw new LastCallMismatch(target.getInvocation(), invocation, invocations); + } + } + } + throw new RuntimeException(target + " never invoked"); + } + + @Override + public VerificationMode description(String description) { + return VerificationModeFactory.description(this, description); + } + + static class LastCallMismatch extends RuntimeException { + LastCallMismatch( + Invocation expected, Invocation received, List<Invocation> allInvocations) { + super("Expected invocation " + expected + " but received " + received + + " as the last invocation.\nAll registered invocations:\n" + allInvocations); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java new file mode 100644 index 000000000000..1a66970a8dc2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java @@ -0,0 +1,64 @@ +/* + * 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.wm.utils; + +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +import com.android.server.wm.AnimationAdapter; +import com.android.server.wm.SurfaceAnimator; + +import java.io.PrintWriter; + +/** + * An empty animation adapter which just executes the finish callback + */ +public class MockAnimationAdapter implements AnimationAdapter { + + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + // As the animation won't run, finish it immediately + finishCallback.onAnimationFinished(0, null); + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) {} + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return 0; + } + + @Override + public void dump(PrintWriter pw, String prefix) {} + + @Override + public void dumpDebug(ProtoOutputStream proto) {} +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index f3bf026ddc6e..55b5d11d938a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -53,6 +53,7 @@ import android.app.usage.AppStandbyInfo; import android.app.usage.BroadcastResponseStatsList; import android.app.usage.ConfigurationStats; import android.app.usage.EventStats; +import android.app.usage.Flags; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event; @@ -200,6 +201,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_ON_START = 7; static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8; static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9; + static final int MSG_UID_REMOVED = 10; private final Object mLock = new Object(); private Handler mHandler; @@ -378,11 +380,10 @@ public class UsageStatsService extends SystemService implements IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); - getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter, - null, mHandler); - + getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, + filter, null, /* Handler scheduler */ null); getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL, - new IntentFilter(ACTION_UID_REMOVED), null, mHandler); + new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null); mRealTimeSnapshot = SystemClock.elapsedRealtime(); mSystemTimeSnapshot = System.currentTimeMillis(); @@ -614,7 +615,6 @@ public class UsageStatsService extends SystemService implements if (Intent.ACTION_USER_REMOVED.equals(action)) { if (userId >= 0) { mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); - mResponseStatsTracker.onUserRemoved(userId); } } else if (Intent.ACTION_USER_STARTED.equals(action)) { if (userId >= 0) { @@ -632,9 +632,7 @@ public class UsageStatsService extends SystemService implements return; } - synchronized (mLock) { - mResponseStatsTracker.onUidRemoved(uid); - } + mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget(); } } @@ -1301,6 +1299,8 @@ public class UsageStatsService extends SystemService implements mPendingLaunchTimeChangePackages.remove(userId); } mAppStandby.onUserRemoved(userId); + mResponseStatsTracker.onUserRemoved(userId); + // Cancel any scheduled jobs for this user since the user is being removed. UsageStatsIdleService.cancelPruneJob(getContext(), userId); UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId); @@ -2037,6 +2037,9 @@ public class UsageStatsService extends SystemService implements case MSG_REMOVE_USER: onUserRemoved(msg.arg1); break; + case MSG_UID_REMOVED: + mResponseStatsTracker.onUidRemoved(msg.arg1); + break; case MSG_PACKAGE_REMOVED: onPackageRemoved(msg.arg1, (String) msg.obj); break; @@ -2125,7 +2128,8 @@ public class UsageStatsService extends SystemService implements private boolean canReportUsageStats() { if (isCallingUidSystem()) { - return true; // System UID can always report UsageStats + // System UID can always report UsageStats + return true; } return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) @@ -2621,9 +2625,12 @@ public class UsageStatsService extends SystemService implements return; } - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportChooserSelection"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportChooserSelection"); + } } // Verify if this package exists before reporting an event for it. @@ -2643,9 +2650,17 @@ public class UsageStatsService extends SystemService implements @Override public void reportUserInteraction(String packageName, int userId) { Objects.requireNonNull(packageName); - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportUserInteraction"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportUserInteraction"); + } + } else { + if (!isCallingUidSystem()) { + throw new SecurityException("Only system is allowed to call" + + " reportUserInteraction"); + } } final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 5d2f27dce206..35e2fcfecb6b 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/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 138e575a6872..1c689d0d5ce3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -58,6 +58,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PermissionEnforcer; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -67,6 +68,7 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.os.Trace; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -1286,6 +1288,17 @@ public class VoiceInteractionManagerService extends SystemService { } } + // Enforce permissions that are flag controlled. The flag value decides if the permission + // should be enforced. + private void initAndVerifyDetector_enforcePermissionWithFlags() { + PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class); + if (Flags.voiceActivationPermissionApis()) { + enforcer.enforcePermission( + android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO, + getCallingPid(), getCallingUid()); + } + } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) @Override public void initAndVerifyDetector( @@ -1295,7 +1308,13 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull IBinder token, IHotwordRecognitionStatusCallback callback, int detectorType) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder, + // IHotwordRecognitionStatusCallback, int)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully + // launched. super.initAndVerifyDetector_enforcePermission(); + initAndVerifyDetector_enforcePermissionWithFlags(); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 971fc781a719..e20e4d200251 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -202,6 +202,24 @@ public final class BarringInfo implements Parcelable { && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds; } + private static String barringTypeToString(@BarringType int barringType) { + return switch (barringType) { + case BARRING_TYPE_NONE -> "NONE"; + case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL"; + case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL"; + case BARRING_TYPE_UNKNOWN -> "UNKNOWN"; + default -> "UNKNOWN(" + barringType + ")"; + }; + } + + @Override + public String toString() { + return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType) + + ", mIsConditionallyBarred=" + mIsConditionallyBarred + + ", mConditionalBarringFactor=" + mConditionalBarringFactor + + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}"; + } + /** @hide */ public BarringServiceInfo(Parcel p) { mBarringType = p.readInt(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 63e91ad414de..7a0bf9038f7c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10523,6 +10523,8 @@ public class CarrierConfigManager { 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( "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428}); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 90fa69ff8a15..73220c353b36 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15033,15 +15033,6 @@ public class TelephonyManager { @TestApi public static final int HAL_SERVICE_IMS = 7; - /** - * HAL service type that supports the HAL APIs implementation of IRadioSatellite - * {@link RadioSatelliteProxy} - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - public static final int HAL_SERVICE_SATELLITE = 8; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"HAL_SERVICE_"}, @@ -15054,7 +15045,6 @@ public class TelephonyManager { HAL_SERVICE_SIM, HAL_SERVICE_VOICE, HAL_SERVICE_IMS, - HAL_SERVICE_SATELLITE }) public @interface HalService {} diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl new file mode 100644 index 000000000000..54cab48dd1e4 --- /dev/null +++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 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.telephony.satellite; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Interface for non-terrestrial signal strength notify callback. + * @hide + */ +oneway interface INtnSignalStrengthCallback { + /** + * Called when NTN signal strength changes. + * @param ntnSignalStrength The new NTN signal strength. + */ + void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl new file mode 100644 index 000000000000..a79cb695ef24 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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.telephony.satellite; + +parcelable NtnSignalStrength; diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java new file mode 100644 index 000000000000..16d765455d21 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java @@ -0,0 +1,149 @@ +/* + * 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.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.telephony.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * NTN signal strength related information. + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public final class NtnSignalStrength implements Parcelable { + /** Non-terrestrial network signal strength is not available. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_NONE = 0; + /** Non-terrestrial network signal strength is poor. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_POOR = 1; + /** Non-terrestrial network signal strength is moderate. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; + /** Non-terrestrial network signal strength is good. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; + /** Non-terrestrial network signal strength is great. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; + @NtnSignalStrengthLevel private int mLevel; + + /** @hide */ + @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = { + NTN_SIGNAL_STRENGTH_NONE, + NTN_SIGNAL_STRENGTH_POOR, + NTN_SIGNAL_STRENGTH_MODERATE, + NTN_SIGNAL_STRENGTH_GOOD, + NTN_SIGNAL_STRENGTH_GREAT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NtnSignalStrengthLevel {} + + /** + * Create a parcelable object to inform the current non-terrestrial signal strength + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public NtnSignalStrength(@NtnSignalStrengthLevel int level) { + this.mLevel = level; + } + + /** + * This constructor is used to create a copy of an existing NtnSignalStrength object. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public NtnSignalStrength(@Nullable NtnSignalStrength source) { + this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel(); + } + + private NtnSignalStrength(Parcel in) { + readFromParcel(in); + } + + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NtnSignalStrengthLevel public int getLevel() { + return mLevel; + } + + /** + * @return 0 + */ + @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public int describeContents() { + return 0; + } + + /** + * @param out The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mLevel); + } + + private void readFromParcel(Parcel in) { + mLevel = in.readInt(); + } + + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull public static final Creator<NtnSignalStrength> CREATOR = + new Creator<NtnSignalStrength>() { + @Override public NtnSignalStrength createFromParcel(Parcel in) { + return new NtnSignalStrength(in); + } + + @Override public NtnSignalStrength[] newArray(int size) { + return new NtnSignalStrength[size]; + } + }; + + @Override + public int hashCode() { + return mLevel; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + NtnSignalStrength that = (NtnSignalStrength) obj; + return mLevel == that.mLevel; + } + + @Override public String toString() { + return "NtnSignalStrength{" + + "mLevel=" + mLevel + + '}'; + } +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java new file mode 100644 index 000000000000..4b79590b9bc6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java @@ -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 android.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.telephony.flags.Flags; + +/** + * A callback class for notifying satellite signal strength change. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public interface NtnSignalStrengthCallback { + /** + * Called when non-terrestrial network signal strength changes. + * @param ntnSignalStrength The new non-terrestrial network signal strength. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 7322aeb8bfca..21d93bd3077f 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -35,7 +35,9 @@ import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ResultReceiver; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyFrameworkInitializer; +import android.telephony.TelephonyManager; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.ITelephony; @@ -77,6 +79,8 @@ public final class SatelliteManager { private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback, ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback> + sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>(); private final int mSubId; @@ -192,6 +196,14 @@ public final class SatelliteManager { public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** + * Bundle key to get the response from + * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}. + * @hide + */ + + public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength"; + + /** * The request was successfully processed. */ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1866,6 +1878,165 @@ public final class SatelliteManager { return new HashSet<>(); } + /** + * Request to get the signal strength of the satellite connection. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * </p> + * + * @param executor The executor on which the callback will be called. + * @param callback The callback object to which the result will be delivered. If the request is + * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of + * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel} + * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no + * signal strength data available. + * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a + * {@link SatelliteException} with the {@link SatelliteResult}. + * + * @throws SecurityException if the caller doesn't have required permission. + * @throws IllegalStateException if the Telephony process is not currently available or + * satellite is not supported. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_RESULT_SUCCESS) { + if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) { + NtnSignalStrength ntnSignalStrength = + resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH, + NtnSignalStrength.class); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onResult(ntnSignalStrength))); + } else { + loge("KEY_NTN_SIGNAL_STRENGTH does not exist."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException( + SATELLITE_RESULT_REQUEST_FAILED)))); + } + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException(resultCode)))); + } + } + }; + telephony.requestNtnSignalStrength(mSubId, receiver); + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("requestNtnSignalStrength() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Registers for NTN signal strength changed from satellite modem. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * </p> + * + * @param executor The executor on which the callback will be called. + * @param callback The callback to handle the NTN signal strength changed event. + * + * @return The {@link SatelliteResult} result of the operation. + * + * @throws SecurityException if the caller doesn't have required permission. + * @throws IllegalStateException if the Telephony process is not currently available. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SatelliteResult public int registerForNtnSignalStrengthChanged( + @NonNull @CallbackExecutor Executor executor, + @NonNull NtnSignalStrengthCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + INtnSignalStrengthCallback internalCallback = + new INtnSignalStrengthCallback.Stub() { + @Override + public void onNtnSignalStrengthChanged( + NtnSignalStrength ntnSignalStrength) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onNtnSignalStrengthChanged( + ntnSignalStrength))); + } + }; + sNtnSignalStrengthCallbackMap.put(callback, internalCallback); + return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + return SATELLITE_RESULT_REQUEST_FAILED; + } + + /** + * Unregisters for NTN signal strength changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}.. + * </p> + * + * @param callback The callback that was passed to + * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. + * + * @throws SecurityException if the caller doesn't have required permission. + * @throws IllegalStateException if the Telephony process is not currently available. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) { + Objects.requireNonNull(callback); + INtnSignalStrengthCallback internalCallback = + sNtnSignalStrengthCallbackMap.remove(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + if (internalCallback != null) { + telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback); + } else { + loge("unregisterForNtnSignalStrengthChanged: No internal callback."); + } + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + + } + + private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl new file mode 100644 index 000000000000..b7712bd83cf6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl @@ -0,0 +1,28 @@ +/* + * 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.telephony.satellite.stub; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Consumer pattern for a request that receives the signal strength of non-terrestrial network from + * the SatelliteService. + * @hide + */ +oneway interface INtnSignalStrengthConsumer { + void accept(in NtnSignalStrength result); +} diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 7fda550c599c..6b47db1e2251 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -16,6 +16,7 @@ package android.telephony.satellite.stub; +import android.telephony.satellite.stub.INtnSignalStrengthConsumer; import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer; import android.telephony.satellite.stub.ISatelliteListener; import android.telephony.satellite.stub.SatelliteDatagram; @@ -454,4 +455,44 @@ oneway interface ISatellite { */ void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback, in IBooleanConsumer callback); + + /** + * Request to get the signal strength of the satellite connection. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + * @param callback The callback to handle the NTN signal strength changed event. + */ + void requestSignalStrength(in IIntegerConsumer resultCallback, + in INtnSignalStrengthConsumer callback); + + /** + * The satellite service should report the NTN signal strength via + * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes. + * + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + */ + void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback); + + /** + * The satellite service should stop reporting NTN signal strength to the framework. This will + * be called when device is screen off to save power by not letting signal strength updates to + * wake up application processor. + * + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + */ + void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback); } diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl index d68716291b8e..d44ddfa1ee7f 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -16,6 +16,7 @@ package android.telephony.satellite.stub; +import android.telephony.satellite.stub.NtnSignalStrength; import android.telephony.satellite.stub.NTRadioTechnology; import android.telephony.satellite.stub.PointingInfo; import android.telephony.satellite.stub.SatelliteDatagram; @@ -58,4 +59,10 @@ oneway interface ISatelliteListener { * @param state The current satellite modem state. */ void onSatelliteModemStateChanged(in SatelliteModemState state); + + /** + * Called when NTN signal strength changes. + * @param ntnSignalStrength The new NTN signal strength. + */ + void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); } diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl new file mode 100644 index 000000000000..f48900583167 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl @@ -0,0 +1,30 @@ +/* + * 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.telephony.satellite.stub; + +import android.telephony.satellite.stub.NtnSignalStrengthLevel; + +/** + * @hide + */ +parcelable NtnSignalStrength { + /** + * Non-terrestrial signal strength. The value represents the level of signal strength which can + * be translated to the number of signal bars. + */ + NtnSignalStrengthLevel signalStrengthLevel; +} diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl new file mode 100644 index 000000000000..53b13733941c --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl @@ -0,0 +1,29 @@ +/* + * 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.telephony.satellite.stub; + +/** + * {@hide} + */ +@Backing(type="int") +enum NtnSignalStrengthLevel { + NTN_SIGNAL_STRENGTH_NONE = 0, + NTN_SIGNAL_STRENGTH_POOR = 1, + NTN_SIGNAL_STRENGTH_MODERATE = 2, + NTN_SIGNAL_STRENGTH_GOOD = 3, + NTN_SIGNAL_STRENGTH_GREAT = 4 +} diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index 4cee01e8bd39..a636a6128e61 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -241,6 +241,30 @@ public class SatelliteImplBase extends SatelliteService { "requestIsSatelliteEnabledForCarrier"); } + @Override + public void requestSignalStrength(IIntegerConsumer resultCallback, + INtnSignalStrengthConsumer callback) throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback), + "requestSignalStrength"); + } + + @Override + public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback) + throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback), + "startSendingNtnSignalStrength"); + } + + @Override + public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback) + throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback), + "stopSendingNtnSignalStrength"); + } + // Call the methods with a clean calling identity on the executor and wait indefinitely for // the future to return. private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException { @@ -728,4 +752,35 @@ public class SatelliteImplBase extends SatelliteService { @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } + + /** + * Request to get the signal strength of the satellite connection. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + * @param callback The callback to handle the NTN signal strength changed event. + */ + public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback, + INtnSignalStrengthConsumer callback) { + // stub implementation + } + + /** + * Requests to deliver signal strength changed events through the + * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)} + * callback. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + */ + public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) { + // stub implementation + } + + /** + * Requests to stop signal strength changed events + * + * @param resultCallback The {@link SatelliteError} result of the operation. + */ + public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){ + // stub implementation + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 3aa5a5a14bc8..58e702688bd3 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -67,10 +67,12 @@ import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.telephony.ims.aidl.IRcsConfigCallback; +import android.telephony.satellite.INtnSignalStrengthCallback; import android.telephony.satellite.ISatelliteDatagramCallback; import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; import android.telephony.satellite.ISatelliteProvisionStateCallback; import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.NtnSignalStrength; import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; import com.android.ims.internal.IImsServiceFeatureCallback; @@ -2837,7 +2839,6 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback); - /** * Registers for provision state changed from satellite modem. * @@ -3071,4 +3072,40 @@ interface ITelephony { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId); + + /** + * Request to get the signal strength of the satellite connection. + * + * @param subId The subId of the subscription to request for. + * @param receiver Result receiver to get the error code of the request and the current signal + * strength of the satellite connection. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void requestNtnSignalStrength(int subId, in ResultReceiver receiver); + + /** + * Registers for NTN signal strength changed from satellite modem. + * + * @param subId The subId of the subscription to request for. + * @param callback The callback to handle the NTN signal strength changed event. + * + * @return The {@link SatelliteResult} result of the operation. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback); + + /** + * Unregisters for NTN signal strength changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for provision state changed. + * @param callback The callback that was passed to + * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void unregisterForNtnSignalStrengthChanged(int subId, + in INtnSignalStrengthCallback callback); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 72e4389e0788..a20f26c1807b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -120,31 +120,6 @@ public interface RILConstants { int BLOCKED_DUE_TO_CALL = 69; /* SMS is blocked due to call control */ int RF_HARDWARE_ISSUE = 70; /* RF HW issue is detected */ int NO_RF_CALIBRATION_INFO = 71; /* No RF calibration in device */ - int ENCODING_NOT_SUPPORTED = 72; /* The encoding scheme is not supported by - either the network or the MS. */ - int FEATURE_NOT_SUPPORTED = 73; /* The requesting feature is not supported by - the service provider. */ - int INVALID_CONTACT = 74; /* The contact to be added is either not - existing or not valid. */ - int MODEM_INCOMPATIBLE = 75; /* The modem of the MS is not compatible with - the service provider. */ - int NETWORK_TIMEOUT = 76; /* Modem timeout to receive ACK or response from - network after sending a request to it. */ - int NO_SATELLITE_SIGNAL = 77; /* Modem fails to communicate with the satellite - network since there is no satellite signal.*/ - int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78; /* The request cannot be performed since the - subscriber's account balance is not - sufficient. */ - int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79; /* The radio technology is not supported by the - service provider. */ - int SUBSCRIBER_NOT_AUTHORIZED = 80; /* The subscription is not authorized to - register with the service provider. */ - int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the - Framework the satellite modem detects - terrestrial signal, aborts the request, and - switches to the terrestrial network. */ - int UNIDENTIFIED_SUBSCRIBER = 82; /* The subscriber is not registered with the - service provider */ // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to // reveal particular replacement for Generic failure @@ -568,22 +543,7 @@ public interface RILConstants { int RIL_REQUEST_IS_N1_MODE_ENABLED = 242; int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243; int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244; - int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245; - int RIL_REQUEST_SET_SATELLITE_POWER = 246; - int RIL_REQUEST_GET_SATELLITE_POWER = 247; - int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248; - int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249; - int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250; - int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251; - int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252; - int RIL_REQUEST_GET_SATELLITE_MODE = 253; - int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254; - int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255; - int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256; - int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257; - int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258; - int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259; - int RIL_REQUEST_SET_SATELLITE_PLMN = 260; + int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -645,13 +605,6 @@ public interface RILConstants { int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053; int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054; int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055; - int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056; - int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057; - int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058; - int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059; - int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060; - int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061; - int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062; /* The following unsols are not defined in RIL.h */ int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index 241e69106718..f61cce666cca 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.test.mock { @Deprecated public class MockAccountManager { @@ -202,7 +200,7 @@ package android.test.mock { method @Deprecated public int checkPermission(String, String); method @Deprecated public int checkSignatures(String, String); method @Deprecated public int checkSignatures(int, int); - method public void clearInstantAppCookie(); + method @Deprecated public void clearInstantAppCookie(); method @Deprecated public void clearPackagePreferredActivities(String); method @Deprecated public String[] currentToCanonicalPackageNames(String[]); method @Deprecated public void extendVerificationTimeout(int, int, long); @@ -224,15 +222,15 @@ package android.test.mock { method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo); method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo); method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public android.content.pm.ChangedPackages getChangedPackages(int); + method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int); method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName); method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon(); method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo); method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method @Deprecated public String getInstallerPackageName(String); - method public byte[] getInstantAppCookie(); - method public int getInstantAppCookieMaxBytes(); + method @Deprecated public byte[] getInstantAppCookie(); + method @Deprecated public int getInstantAppCookieMaxBytes(); method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public android.content.Intent getLaunchIntentForPackage(String); method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String); @@ -241,7 +239,7 @@ package android.test.mock { method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public android.content.pm.PackageInstaller getPackageInstaller(); + method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller(); method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public String[] getPackagesForUid(int); method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int); @@ -265,8 +263,8 @@ package android.test.mock { method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo); method @Deprecated public boolean hasSystemFeature(String); method @Deprecated public boolean hasSystemFeature(String, int); - method public boolean isInstantApp(); - method public boolean isInstantApp(String); + method @Deprecated public boolean isInstantApp(); + method @Deprecated public boolean isInstantApp(String); method @Deprecated public boolean isPermissionRevokedByPolicy(String, String); method @Deprecated public boolean isSafeMode(); method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -283,11 +281,12 @@ package android.test.mock { method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int); method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int); - method public void setApplicationCategoryHint(String, int); + method @Deprecated public void setApplicationCategoryHint(String, int); method @Deprecated public void setApplicationEnabledSetting(String, int, int); method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int); method @Deprecated public void setInstallerPackageName(String, String); - method public void updateInstantAppCookie(@NonNull byte[]); + method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]); + method @Deprecated public void updateInstantAppCookie(@NonNull byte[]); method @Deprecated public void verifyPendingInstall(int, int); } diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt index fa2fbd276e9a..0c800b6f82b5 100644 --- a/test-mock/api/removed.txt +++ b/test-mock/api/removed.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { @@ -11,7 +9,7 @@ package android.test.mock { @Deprecated public class MockPackageManager extends android.content.pm.PackageManager { method @Deprecated public String getDefaultBrowserPackageName(int); method @Deprecated public boolean setDefaultBrowserPackageName(String, int); - method public boolean setInstantAppCookie(@NonNull byte[]); + method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]); } } diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt index 2b5132ecd14f..f35095743738 100644 --- a/test-mock/api/system-current.txt +++ b/test-mock/api/system-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { @@ -11,30 +9,30 @@ package android.test.mock { } @Deprecated public class MockPackageManager extends android.content.pm.PackageManager { - method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); - method public boolean arePermissionsIndividuallyControlled(); + method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); + method @Deprecated public boolean arePermissionsIndividuallyControlled(); method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String); - method public String getDefaultBrowserPackageNameAsUser(int); - method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); - method public android.graphics.drawable.Drawable getInstantAppIcon(String); - method public android.content.ComponentName getInstantAppInstallerComponent(); - method public android.content.ComponentName getInstantAppResolverSettingsComponent(); - method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); - method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String); - method public int getIntentVerificationStatusAsUser(String, int); - method public int getPermissionFlags(String, String, android.os.UserHandle); - method public void grantRuntimePermission(String, String, android.os.UserHandle); - method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); - method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); - method public void revokeRuntimePermission(String, String, android.os.UserHandle); - method public boolean setDefaultBrowserPackageNameAsUser(String, int); + method @Deprecated public String getDefaultBrowserPackageNameAsUser(int); + method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); + method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String); + method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent(); + method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent(); + method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); + method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String); + method @Deprecated public int getIntentVerificationStatusAsUser(String, int); + method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle); + method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle); + method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException; + method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); + method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); + method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle); + method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int); method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String); - method public void setUpdateAvailable(String, boolean); - method public boolean updateIntentVerificationStatusAsUser(String, int, int); - method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle); - method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); + method @Deprecated public void setUpdateAvailable(String, boolean); + method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int); + method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle); + method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); } } diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/test-mock/api/system-removed.txt +++ b/test-mock/api/system-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index 1752edcd469e..9ed010881067 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -1,6 +1,4 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { @@ -8,13 +6,13 @@ package android.test.mock { } @Deprecated public class MockPackageManager extends android.content.pm.PackageManager { - method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int); - method public void clearCrossProfileIntentFilters(int); - method public int getInstallReason(String, android.os.UserHandle); - method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); - method public String[] getNamesForUids(int[]); - method @NonNull public String getServicesSystemSharedLibraryPackageName(); - method @NonNull public String getSharedSystemSharedLibraryPackageName(); + method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int); + method @Deprecated public void clearCrossProfileIntentFilters(int); + method @Deprecated public int getInstallReason(String, android.os.UserHandle); + method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); + method @Deprecated public String[] getNamesForUids(int[]); + method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName(); + method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName(); } } diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/test-mock/api/test-removed.txt +++ b/test-mock/api/test-removed.txt @@ -1,3 +1 @@ // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 79348d104745..ae7c2a99b808 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -247,7 +247,7 @@ public class GraphicsActivity extends Activity { int rc = 0; try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { - transaction.setFrameRateCategory(mSurfaceControl, category); + transaction.setFrameRateCategory(mSurfaceControl, category, false); transaction.apply(); } return rc; diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 63acddf25e02..0f4798090da8 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/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index 359845dc0de6..47d6d235848f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -36,7 +36,8 @@ import org.junit.runners.Parameterized /** * Test launching a secondary Activity into Picture-In-Picture mode. * - * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom * right corner on screen. * * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest` @@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : } } - /** Main and secondary activity start from a split each taking half of the screen. */ + /** + * We expect the background layer to be visible during this transition. + */ + @Presubmit + @Test + override fun backgroundLayerNeverVisible(): Unit {} + + /** + * Main and secondary activity start from a split each taking half of the screen. + */ @Presubmit @Test fun layersStartFromEqualSplit() { @@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : .isVisible(TRANSITION_SNAPSHOT) .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) .then() - .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true) } flicker.assertLayersEnd { visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) 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 56ab755af47b..7e43566d56f8 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() } diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp index bfd35083366e..c901efa707f4 100644 --- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp +++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptAutoVersionTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp index 7db9d2698cc7..d0649ea4ef9c 100644 --- a/tools/aapt2/integration-tests/BasicTest/Android.bp +++ b/tools/aapt2/integration-tests/BasicTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptBasicTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp index 80404eeb8d8e..ebb4e9f479d6 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp @@ -24,9 +24,9 @@ package { } android_test { - name: "AaptTestStaticLib_App", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], asset_dirs: [ "assets", diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp index a84da43c70c8..ee12a92906a8 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp @@ -26,6 +26,7 @@ package { android_library { name: "AaptTestStaticLib_LibOne", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], resource_dirs: ["res"], } diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp index d386c3a35d20..83b2362496fc 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp @@ -26,6 +26,7 @@ package { android_library { name: "AaptTestStaticLib_LibTwo", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], resource_dirs: ["res"], libs: ["AaptTestStaticLib_LibOne"], diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp index 1e8cf86ed811..15a6a207d6d1 100644 --- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp +++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptSymlinkTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index a617876a6da0..226e2fadf735 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -7,9 +7,38 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +// Visibility only for ravenwood prototype uses. +genrule_defaults { + name: "hoststubgen-for-prototype-only-genrule", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +java_defaults { + name: "hoststubgen-for-prototype-only-java", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +filegroup_defaults { + name: "hoststubgen-for-prototype-only-filegroup", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + // This library contains the standard hoststubgen annotations. +// This is only for the prototype. The productionized version is "ravenwood-annotations". java_library { name: "hoststubgen-annotations", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "annotations-src/**/*.java", ], @@ -18,7 +47,6 @@ java_library { // Seems like we need it to avoid circular deps. // Copied it from "app-compat-annotations". sdk_version: "core_current", - visibility: ["//visibility:public"], } // This library contains helper classes used in the host side test environment at runtime. @@ -30,11 +58,6 @@ java_library_host { ], libs: [ "junit", - "ow2-asm", - "ow2-asm-analysis", - "ow2-asm-commons", - "ow2-asm-tree", - "ow2-asm-util", ], static_libs: [ "guava", @@ -60,12 +83,13 @@ java_binary_host { } // File that contains the standard command line argumetns to hoststubgen. +// This is only for the prototype. The productionized version is "ravenwood-standard-options". filegroup { name: "hoststubgen-standard-options", + defaults: ["hoststubgen-for-prototype-only-filegroup"], srcs: [ "hoststubgen-standard-options.txt", ], - visibility: ["//visibility:public"], } hoststubgen_common_options = "$(location hoststubgen) " + @@ -98,7 +122,6 @@ genrule_defaults { "hoststubgen_keep_all.txt", "hoststubgen_dump.txt", ], - // visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with hidden APIs. @@ -116,8 +139,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_stub.jar}", @@ -125,12 +150,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_impl.jar}", @@ -138,11 +164,11 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with only public/system/test APIs, without // hidden APIs. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host", defaults: ["hoststubgen-command-defaults"], @@ -159,8 +185,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_stub.jar}", @@ -168,12 +196,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_impl.jar}", @@ -181,7 +210,6 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // This library contains helper classes to build hostside tests/targets. @@ -191,6 +219,7 @@ java_genrule_host { // Ideally this library should be empty. java_library_host { name: "hoststubgen-helper-framework-buildtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-buildtime-src/**/*.java", ], @@ -200,13 +229,13 @@ java_library_host { "framework-all-hidden-api-host-impl", "junit", ], - visibility: ["//visibility:public"], } // This module contains "fake" libcore/dalvik classes, framework native substitution, etc, // that are needed at runtime. java_library_host { name: "hoststubgen-helper-framework-runtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-runtime-src/**/*.java", ], @@ -214,7 +243,6 @@ java_library_host { "hoststubgen-helper-runtime", "framework-all-hidden-api-host-impl", ], - visibility: ["//visibility:public"], } // Defaults for host side test modules. diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java index 4c37579ac917..7ada961d710f 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java @@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper; import static java.lang.annotation.ElementType.TYPE; -import org.objectweb.asm.Type; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -29,6 +27,6 @@ import java.lang.annotation.Target; @Target({TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface HostStubGenProcessedKeepClass { - String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class); + String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class); String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";"; } diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java index 34e0030f548a..e598da0a3cb9 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java @@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper; import static java.lang.annotation.ElementType.TYPE; -import org.objectweb.asm.Type; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -29,6 +27,6 @@ import java.lang.annotation.Target; @Target({TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface HostStubGenProcessedStubClass { - String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class); + String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class); String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";"; } diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index d176b5e97b0b..9f835979b238 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java @@ -15,8 +15,6 @@ */ package com.android.hoststubgen.hosthelper; -import org.objectweb.asm.Type; - import java.io.PrintStream; import java.lang.StackWalker.Option; import java.lang.reflect.Method; @@ -32,7 +30,15 @@ public class HostTestUtils { private HostTestUtils() { } - public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class); + /** + * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM + * in this JAR. + */ + public static String getInternalName(final Class<?> clazz) { + return clazz.getName().replace('.', '/'); + } + + public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class); /** If true, we won't print method call log. */ private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv( diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh index 722905f15b41..6bf074b1bf9f 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh @@ -16,14 +16,8 @@ source "${0%/*}"/../../common.sh -#********************************************************************************************** -#This script is broken because it relies on soong intermediate files, which seem to have moved. -#********************************************************************************************** - # This scripts run the "tiny-framework" test, but does most stuff from the command line, using # the native java and javac commands. -# This is useful to - debug=0 while getopts "d" opt; do @@ -61,7 +55,7 @@ framework_compile_classpaths=( test_compile_classpaths=( $SOONG_INT/external/junit/junit/android_common/combined/junit.jar - $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar + $ANDROID_HOST_OUT/framework/truth-prebuilt.jar ) test_runtime_classpaths=( diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 246d06526f5b..cd604ff97f73 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -23,6 +23,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.FileDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + public class TinyFrameworkClassTest { @Rule public ExpectedException thrown = ExpectedException.none(); @@ -140,4 +144,42 @@ public class TinyFrameworkClassTest { assertThat(TinyFrameworkClassLoadHook.sLoadedClasses) .doesNotContain(TinyFrameworkNestedClasses.class); } + + /** + * Test to try accessing JDK private fields using reflections + setAccessible(true), + * which is now disallowed due to Java Modules, unless you run the javacommand with. + * --add-opens=java.base/java.io=ALL-UNNAMED + * + * You can try it from the command line, like: + * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh + * + * @throws Exception + */ + @Test + public void testFileDescriptor() throws Exception { + var fd = FileDescriptor.out; + + // Get the FD value directly from the private field. + // This is now prohibited due to Java Modules. + // It throws: + // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa + + thrown.expect(java.lang.reflect.InaccessibleObjectException.class); + + // Access the private field. + final Field f = FileDescriptor.class.getDeclaredField("fd"); + final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class); + f.setAccessible(true); + m.setAccessible(true); + + assertThat(f.get(fd)).isEqualTo(1); + + // Set + f.set(fd, 2); + assertThat(f.get(fd)).isEqualTo(2); + + // Call the package private method, set(int). + m.invoke(fd, 0); + assertThat(f.get(fd)).isEqualTo(0); + } } diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 2e9cf428354a..8bc88de608e5 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -34,8 +34,7 @@ run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh run ./hoststubgen/test-framework/run-test-without-atest.sh -#This script is broken because it relies on soong intermediate files, which seem to have moved. -#run ./hoststubgen/test-tiny-framework/run-test-manually.sh +run ./hoststubgen/test-tiny-framework/run-test-manually.sh run atest tiny-framework-dump-test run ./scripts/build-framework-hostside-jars-and-extract.sh diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp index ca96559ac016..40281d263a4c 100644 --- a/tools/lint/global/integration_tests/Android.bp +++ b/tools/lint/global/integration_tests/Android.bp @@ -12,25 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. -java_library { - name: "AndroidGlobalLintTestNoAidl", - srcs: ["TestNoAidl/**/*.java"], +// Integration tests for @EnforcePermission linters. +// Each test defines its own java_library. The XML lint report from this +// java_library is wrapped under a Python library with a unique pkg_path (this +// is to avoid a name conflict for the report file). All the tests are +// referenced and executed by AndroidGlobalLintCheckerIntegrationTest. + +java_defaults { + name: "AndroidGlobalLintIntegrationTestDefault", libs: [ "framework-annotations-lib", ], lint: { - // It is expected that lint returns an error when processing this + // It is expected that lint returns an error when processing the // library. Silence it here, the lint output is verified in tests.py. suppress_exit_code: true, }, } +java_library { + name: "AndroidGlobalLintTestNoAidl", + srcs: ["TestNoAidl/**/*.java"], + defaults: ["AndroidGlobalLintIntegrationTestDefault"], +} + +python_library_host { + name: "AndroidGlobalLintTestNoAidl_py", + data: [":AndroidGlobalLintTestNoAidl{.lint}"], + pkg_path: "no_aidl", +} + +java_library { + name: "AndroidGlobalLintTestMissingAnnotation", + srcs: [ + "TestMissingAnnotation/**/*.java", + "TestMissingAnnotation/**/*.aidl", + ], + defaults: ["AndroidGlobalLintIntegrationTestDefault"], +} + +python_library_host { + name: "AndroidGlobalLintTestMissingAnnotation_py", + data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"], + pkg_path: "missing_annotation", +} + python_test_host { name: "AndroidGlobalLintCheckerIntegrationTest", srcs: ["tests.py"], main: "tests.py", - data: [ - ":AndroidGlobalLintTestNoAidl{.lint}", + libs: [ + "AndroidGlobalLintTestNoAidl_py", + "AndroidGlobalLintTestMissingAnnotation_py", ], version: { py3: { diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java new file mode 100644 index 000000000000..9e4854c61f96 --- /dev/null +++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java @@ -0,0 +1,28 @@ +/* + * 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.google.android.lint.integration_tests; + +/** + * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation. + */ +class TestMissingAnnotation extends IFoo.Stub { + + @Override + public void Method() { + } + +} diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl new file mode 100644 index 000000000000..95ec2c230599 --- /dev/null +++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl @@ -0,0 +1,7 @@ +package com.google.android.lint.integration_tests; + +interface IFoo { + + @EnforcePermission("INTERNET") + void Method(); +} diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py index fc3eeb4f8ed9..cdb16b8ba25f 100644 --- a/tools/lint/global/integration_tests/tests.py +++ b/tools/lint/global/integration_tests/tests.py @@ -19,16 +19,28 @@ import xml.etree.ElementTree class TestLinterReports(unittest.TestCase): """Integration tests for the linters used by @EnforcePermission.""" - def test_no_aidl(self): - report = pkgutil.get_data("lint", "lint-report.xml").decode() + def _read_report(self, pkg_path): + report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode() issues = xml.etree.ElementTree.fromstring(report) self.assertEqual(issues.tag, "issues") + return issues + + def test_no_aidl(self): + issues = self._read_report("no_aidl") self.assertEqual(len(issues), 1) issue = issues[0] self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation") self.assertEqual(issue.attrib["severity"], "Error") + def test_missing_annotation(self): + issues = self._read_report("missing_annotation") + self.assertEqual(len(issues), 1) + + issue = issues[0] + self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation") + self.assertEqual(issue.attrib["severity"], "Error") + if __name__ == '__main__': unittest.main(verbosity=2) |