diff options
519 files changed, 10903 insertions, 7889 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index db515379c8b9..75fb215b8d45 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -14,6 +14,7 @@ aconfig_srcjars = [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.app.smartspace.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}", @@ -599,3 +600,16 @@ java_aconfig_library { aconfig_declarations: "android.service.notification.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Smartspace +aconfig_declarations { + name: "android.app.smartspace.flags-aconfig", + package: "android.app.smartspace.flags", + srcs: ["core/java/android/app/smartspace/flags.aconfig"], +} + +java_aconfig_library { + name: "android.app.smartspace.flags-aconfig-java", + aconfig_declarations: "android.app.smartspace.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 7d5e788c5a37..986a07108b91 100644 --- a/Android.bp +++ b/Android.bp @@ -614,30 +614,6 @@ java_library { ], } -// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp -metalava_framework_docs_args = "" + - "--api-lint-ignore-prefix android.icu. " + - "--api-lint-ignore-prefix java. " + - "--api-lint-ignore-prefix junit. " + - "--api-lint-ignore-prefix org. " + - "--error NoSettingsProvider " + - "--error UnhiddenSystemApi " + - "--error UnflaggedApi " + - "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " + - "--hide BroadcastBehavior " + - "--hide CallbackInterface " + - "--hide DeprecationMismatch " + - "--hide HiddenSuperclass " + - "--hide HiddenTypeParameter " + - "--hide MissingPermission " + - "--hide-package android.audio.policy.configuration.V7_0 " + - "--hide-package com.android.server " + - "--hide RequiresPermission " + - "--hide SdkConstant " + - "--hide Todo " + - "--hide UnavailableSymbol " + - "--manifest $(location :frameworks-base-core-AndroidManifest.xml) " - packages_to_document = [ "android", "dalvik", @@ -706,6 +682,29 @@ stubs_defaults { "android.hardware.vibrator-V1.3-java", "framework-protos", ], + flags: [ + "--api-lint-ignore-prefix android.icu.", + "--api-lint-ignore-prefix java.", + "--api-lint-ignore-prefix junit.", + "--api-lint-ignore-prefix org.", + "--error NoSettingsProvider", + "--error UnhiddenSystemApi", + "--error UnflaggedApi", + "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*", + "--hide BroadcastBehavior", + "--hide CallbackInterface", + "--hide DeprecationMismatch", + "--hide HiddenSuperclass", + "--hide HiddenTypeParameter", + "--hide MissingPermission", + "--hide RequiresPermission", + "--hide SdkConstant", + "--hide Todo", + "--hide UnavailableSymbol", + "--hide-package android.audio.policy.configuration.V7_0", + "--hide-package com.android.server", + "--manifest $(location :frameworks-base-core-AndroidManifest.xml)", + ], filter_packages: packages_to_document, high_mem: true, // Lots of sources => high memory use, see b/170701554 installable: false, diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 3e835b8ad429..eb5502ba95d2 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -3,6 +3,6 @@ package: "com.android.server.job" flag { name: "relax_prefetch_connectivity_constraint_only_on_charger" namespace: "backstage_power" - description: "Only relax a prefetch job's connectivity constraint when the device is charging" + description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low" bug: "299329948" }
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 5bf2eb942a6b..6550f26436d4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1518,8 +1518,8 @@ class JobConcurrencyManager { @WorkType final int workType) { final List<StateController> controllers = mService.mControllers; final int numControllers = controllers.size(); - final PowerManager.WakeLock wl = - mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag()); + final PowerManager.WakeLock wl = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getWakelockTag()); wl.setWorkSource(mService.deriveWorkSource( jobStatus.getSourceUid(), jobStatus.getSourcePackageName())); wl.setReferenceCounted(false); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index bbe1485ddcd4..592aff80f618 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -5512,7 +5512,6 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print("Evaluated bias: "); pw.println(JobInfo.getBiasString(bias)); - pw.print("Tag: "); pw.println(job.getTag()); pw.print("Enq: "); TimeUtils.formatDuration(job.madePending - nowUptime, pw); pw.decreaseIndent(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 4357d4f39dce..293088d9236f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -180,7 +180,7 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case "-u": case "--user": - userId = Integer.parseInt(getNextArgRequired()); + userId = UserHandle.parseUserArg(getNextArgRequired()); break; case "-n": @@ -199,6 +199,10 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return -1; } + if (userId == UserHandle.USER_CURRENT) { + userId = ActivityManager.getCurrentUser(); + } + final String pkgName = getNextArgRequired(); final int jobId = Integer.parseInt(getNextArgRequired()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index f47766ed0393..79653f0e0a91 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -398,7 +398,8 @@ public final class JobServiceContext implements ServiceConnection { // it was inflated from disk with not-yet-coherent delay/deadline bounds. job.clearPersistedUtcTimes(); - mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, job.getTag()); + mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + job.getWakelockTag()); mWakeLock.setWorkSource( mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName())); mWakeLock.setReferenceCounted(false); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 45f15db93a0d..63eaa63db72e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -932,7 +932,9 @@ public final class ConnectivityController extends RestrictingController implemen return false; } if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) { - if (!mService.isBatteryCharging()) { + // Since the constraint relaxation isn't required by the job, only do it when the + // device is charging and the battery level is above the "low battery" threshold. + if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) { return false; } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 458ff35c30ee..1fb54d59179b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -253,7 +253,12 @@ public final class JobStatus { /** An ID that can be used to uniquely identify the job when logging statsd metrics. */ private final long mLoggingJobId; - final String tag; + /** + * Tag to identify the wakelock held for this job. Lazily loaded in + * {@link #getWakelockTag()} since it's not typically needed until the job is about to run. + */ + @Nullable + private String mWakelockTag; /** Whether this job was scheduled by one app on behalf of another. */ final boolean mIsProxyJob; @@ -627,7 +632,6 @@ public final class JobStatus { this.batteryName = this.sourceTag != null ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName() : bnNamespace + job.getService().flattenToShortString(); - this.tag = "*job*/" + this.batteryName + "#" + job.getId(); final String componentPackage = job.getService().getPackageName(); mIsProxyJob = !this.sourcePackageName.equals(componentPackage); @@ -1321,8 +1325,13 @@ public final class JobStatus { return batteryName; } - public String getTag() { - return tag; + /** Return the String to be used as the tag for the wakelock held for this job. */ + @NonNull + public String getWakelockTag() { + if (mWakelockTag == null) { + mWakelockTag = "*job*/" + this.batteryName; + } + return mWakelockTag; } public int getBias() { @@ -2639,7 +2648,7 @@ public final class JobStatus { @NeverCompile // Avoid size overhead of debugging code. public void dump(IndentingPrintWriter pw, boolean full, long nowElapsed) { UserHandle.formatUid(pw, callingUid); - pw.print(" tag="); pw.println(tag); + pw.print(" tag="); pw.println(getWakelockTag()); pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid()); pw.print(" user="); pw.print(getSourceUserId()); @@ -2955,7 +2964,7 @@ public final class JobStatus { final long token = proto.start(fieldId); proto.write(JobStatusDumpProto.CALLING_UID, callingUid); - proto.write(JobStatusDumpProto.TAG, tag); + proto.write(JobStatusDumpProto.TAG, getWakelockTag()); proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid()); proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId()); proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName()); diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index e086bfe5cbb2..5744bdfd4b28 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -72,7 +72,6 @@ droidstubs { "android-non-updatable-doc-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args, } droidstubs { @@ -81,8 +80,7 @@ droidstubs { "android-non-updatable-doc-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", + flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"], } droidstubs { @@ -91,9 +89,10 @@ droidstubs { "android-non-updatable-doc-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) ", + flags: [ + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)", + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)", + ], generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs. write_sdk_values: false, } @@ -104,10 +103,11 @@ droidstubs { "android-non-updatable-doc-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) " + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ", + flags: [ + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)", + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)", + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)", + ], generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs. write_sdk_values: false, } @@ -116,7 +116,6 @@ droidstubs { name: "framework-doc-stubs", defaults: ["android-non-updatable-doc-stubs-defaults"], srcs: [":all-modules-public-stubs-source"], - args: metalava_framework_docs_args, api_levels_module: "api_versions_public", aidl: { include_dirs: [ @@ -129,8 +128,7 @@ droidstubs { droidstubs { name: "framework-doc-system-stubs", defaults: ["framework-doc-stubs-sources-default"], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", + flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"], api_levels_module: "api_versions_system", } @@ -139,30 +137,6 @@ droidstubs { // using droiddoc ///////////////////////////////////////////////////////////////////// -// doclava contains checks for a few issues that are have been migrated to metalava. -// disable them in doclava, to avoid mistriggering or double triggering. -ignore_doclava_errors_checked_by_metalava = "" + - "-hide 111 " + // HIDDEN_SUPERCLASS - "-hide 113 " + // DEPRECATION_MISMATCH - "-hide 125 " + // REQUIRES_PERMISSION - "-hide 126 " + // BROADCAST_BEHAVIOR - "-hide 127 " + // SDK_CONSTANT - "-hide 128 " // TODO - -framework_docs_only_args = "-android " + - "-manifest $(location :frameworks-base-core-AndroidManifest.xml) " + - "-metalavaApiSince " + - "-werror " + - "-lerror " + - ignore_doclava_errors_checked_by_metalava + - "-overview $(location :frameworks-base-java-overview) " + - // Federate Support Library references against local API file. - "-federate SupportLib https://developer.android.com " + - "-federationapi SupportLib $(location :current-support-api) " + - // Federate Support Library references against local API file. - "-federate AndroidX https://developer.android.com " + - "-federationapi AndroidX $(location :current-androidx-api) " - doc_defaults { name: "framework-docs-default", sdk_version: "none", @@ -182,6 +156,28 @@ doc_defaults { resourcesdir: "docs/html/reference/images/", resourcesoutdir: "reference/android/images/", lint_baseline: "javadoc-lint-baseline", + flags: [ + "-android", + "-manifest $(location :frameworks-base-core-AndroidManifest.xml)", + "-metalavaApiSince", + "-werror", + "-lerror", + "-overview $(location :frameworks-base-java-overview)", + // Federate Support Library references against local API file. + "-federate SupportLib https://developer.android.com", + "-federationapi SupportLib $(location :current-support-api)", + // Federate Support Library references against local API file. + "-federate AndroidX https://developer.android.com", + "-federationapi AndroidX $(location :current-androidx-api)", + // doclava contains checks for a few issues that are have been migrated to metalava. + // disable them in doclava, to avoid mistriggering or double triggering. + "-hide 111", // HIDDEN_SUPERCLASS + "-hide 113", // DEPRECATION_MISMATCH + "-hide 125", // REQUIRES_PERMISSION + "-hide 126", // BROADCAST_BEHAVIOR + "-hide 127", // SDK_CONSTANT + "-hide 128", // TODO + ], hdf: [ "dac true", "sdk.codename O", @@ -217,7 +213,10 @@ droiddoc { ], compat_config: ":global-compat-config", proofread_file: "offline-sdk-docs-proofread.txt", - args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"", + flags: [ + "-offlinemode", + "-title \"Android SDK\"", + ], static_doc_index_redirect: "docs/docs-preview-index.html", } @@ -234,7 +233,11 @@ droiddoc { "android.whichdoc offline", ], proofread_file: "offline-sdk-referenceonly-docs-proofread.txt", - args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly", + flags: [ + "-offlinemode", + "-title \"Android SDK\"", + "-referenceonly", + ], static_doc_index_redirect: "docs/docs-documentation-redirect.html", static_doc_properties: "docs/source.properties", } @@ -252,8 +255,14 @@ droiddoc { "android.whichdoc offline", ], proofread_file: "offline-system-sdk-referenceonly-docs-proofread.txt", - args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" + - " -offlinemode -title \"Android System SDK\" -referenceonly", + flags: [ + "-hide 101", + "-hide 104", + "-hide 108", + "-offlinemode", + "-title \"Android System SDK\"", + "-referenceonly", + ], static_doc_index_redirect: "docs/docs-documentation-redirect.html", static_doc_properties: "docs/source.properties", } @@ -269,22 +278,28 @@ droiddoc { "android.hasSamples true", ], proofread_file: "ds-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -yamlV2 -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ", + flags: [ + " -toroot /", + "-yamlV2", + "-samplegroup Admin", + "-samplegroup Background", + "-samplegroup Connectivity", + "-samplegroup Content", + "-samplegroup Input", + "-samplegroup Media", + "-samplegroup Notification", + "-samplegroup RenderScript", + "-samplegroup Security", + "-samplegroup Sensors", + "-samplegroup System", + "-samplegroup Testing", + "-samplegroup UI", + "-samplegroup Views", + "-samplegroup Wearable", + "-devsite", + "-samplesdir", + "development/samples/browseable", + ], } droiddoc { @@ -292,8 +307,11 @@ droiddoc { srcs: [ ":framework-doc-stubs", ], - args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " + + flags: [ + "-noJdkLink", + "-links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list", "-noStdlibLink", + ], proofread_file: "ds-dokka-proofread.txt", dokka_enabled: true, } @@ -346,11 +364,12 @@ droiddoc { hdf: [ "android.whichdoc online", ], - args: framework_docs_only_args + - " -staticonly " + - " -toroot / " + - " -devsite " + - " -ignoreJdLinks ", + flags: [ + "-staticonly", + "-toroot /", + "-devsite", + "-ignoreJdLinks", + ], } droiddoc { @@ -362,8 +381,9 @@ droiddoc { hdf: [ "android.whichdoc online", ], - args: framework_docs_only_args + - " -toroot / " + - " -atLinksNavtree " + - " -navtreeonly ", + flags: [ + "-toroot /", + "-atLinksNavtree", + "-navtreeonly", + ], } diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 7e78185b2659..d566552333cb 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -36,7 +36,6 @@ droidstubs { "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args, check_api: { current: { api_file: ":non-updatable-current.txt", @@ -70,19 +69,25 @@ droidstubs { api_surface: "public", } -priv_apps = " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + - "\\)" +priv_apps = [ + "--show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + + "\\)", +] -priv_apps_in_stubs = " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + - "\\)" +priv_apps_in_stubs = [ + "--show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + + "\\)", +] -test = " --show-annotation android.annotation.TestApi" +test = ["--show-annotation android.annotation.TestApi"] -module_libs = " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + - "\\)" +module_libs = [ + "--show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\)", +] droidstubs { name: "system-api-stubs-docs-non-updatable", @@ -93,7 +98,7 @@ droidstubs { "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + priv_apps, + flags: priv_apps, check_api: { current: { api_file: ":non-updatable-system-current.txt", @@ -136,7 +141,7 @@ droidstubs { "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + test + priv_apps_in_stubs, + flags: test + priv_apps_in_stubs, check_api: { current: { api_file: ":non-updatable-test-current.txt", @@ -186,7 +191,7 @@ droidstubs { "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", ], - args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs, + flags: priv_apps_in_stubs + module_libs, check_api: { current: { api_file: ":non-updatable-module-lib-current.txt", @@ -972,7 +977,7 @@ droidstubs { merge_annotations_dirs: [ "metalava-manual", ], - args: priv_apps, + flags: priv_apps, } java_library { diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh index 71f366a6aae2..e0153f7c1091 100755 --- a/api/gen_combined_removed_dex.sh +++ b/api/gen_combined_removed_dex.sh @@ -6,6 +6,6 @@ shift 2 # Convert each removed.txt to the "dex format" equivalent, and print all output. for f in "$@"; do - "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp" + "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp" cat "${tmp_dir}/tmp" done diff --git a/core/api/current.txt b/core/api/current.txt index d8ea7217fd58..3f1f720f9a4e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -232,6 +232,7 @@ package android { field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG"; field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS"; + field @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") public static final String READ_DROPBOX_DATA = "android.permission.READ_DROPBOX_DATA"; field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"; field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA"; field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE"; @@ -9644,13 +9645,13 @@ package android.companion { method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String); method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); - method @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int); - field public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0 - field public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1 - field public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2 - field public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3 - field public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4 - field public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5 + method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int); + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0 + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1 + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2 + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3 + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4 + field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5 field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; } @@ -11101,7 +11102,7 @@ package android.content { field public static final String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE"; field @Deprecated public static final String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE"; field public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; - field public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL"; field public static final String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT"; field public static final String EXTRA_ASSIST_INPUT_DEVICE_ID = "android.intent.extra.ASSIST_INPUT_DEVICE_ID"; field public static final String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD"; @@ -11988,6 +11989,40 @@ package android.content.pm { method public final int compare(android.content.pm.ApplicationInfo, android.content.pm.ApplicationInfo); } + @FlaggedApi("android.content.pm.archiving") public final class ArchivedActivity { + ctor public ArchivedActivity(@NonNull CharSequence, @NonNull android.content.ComponentName); + method @NonNull public android.content.ComponentName getComponentName(); + method @Nullable public android.graphics.drawable.Drawable getIcon(); + method @NonNull public CharSequence getLabel(); + method @Nullable public android.graphics.drawable.Drawable getMonochromeIcon(); + method @NonNull public android.content.pm.ArchivedActivity setComponentName(@NonNull android.content.ComponentName); + method @NonNull public android.content.pm.ArchivedActivity setIcon(@NonNull android.graphics.drawable.Drawable); + method @NonNull public android.content.pm.ArchivedActivity setLabel(@NonNull CharSequence); + method @NonNull public android.content.pm.ArchivedActivity setMonochromeIcon(@NonNull android.graphics.drawable.Drawable); + } + + @FlaggedApi("android.content.pm.archiving") public final class ArchivedPackage { + ctor public ArchivedPackage(@NonNull String, @NonNull android.content.pm.SigningInfo, @NonNull java.util.List<android.content.pm.ArchivedActivity>); + method @Nullable public String getDefaultToDeviceProtectedStorage(); + method @NonNull public java.util.List<android.content.pm.ArchivedActivity> getLauncherActivities(); + method @NonNull public String getPackageName(); + method @Nullable public String getRequestLegacyExternalStorage(); + method @NonNull public android.content.pm.SigningInfo getSigningInfo(); + method public int getTargetSdkVersion(); + method @Nullable public String getUserDataFragile(); + method public int getVersionCode(); + method public int getVersionCodeMajor(); + method @NonNull public android.content.pm.ArchivedPackage setDefaultToDeviceProtectedStorage(@NonNull String); + method @NonNull public android.content.pm.ArchivedPackage setLauncherActivities(@NonNull java.util.List<android.content.pm.ArchivedActivity>); + method @NonNull public android.content.pm.ArchivedPackage setPackageName(@NonNull String); + method @NonNull public android.content.pm.ArchivedPackage setRequestLegacyExternalStorage(@NonNull String); + method @NonNull public android.content.pm.ArchivedPackage setSigningInfo(@NonNull android.content.pm.SigningInfo); + method @NonNull public android.content.pm.ArchivedPackage setTargetSdkVersion(int); + method @NonNull public android.content.pm.ArchivedPackage setUserDataFragile(@NonNull String); + method @NonNull public android.content.pm.ArchivedPackage setVersionCode(int); + method @NonNull public android.content.pm.ArchivedPackage setVersionCodeMajor(int); + } + public final class Attribution implements android.os.Parcelable { method public int describeContents(); method @IdRes public int getLabel(); @@ -12320,6 +12355,7 @@ package android.content.pm { method @Nullable public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getStagedSessions(); method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.INSTALL_EXISTING_PACKAGES"}) public void installExistingPackage(@NonNull String, int, @Nullable android.content.IntentSender); + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void installPackageArchived(@NonNull android.content.pm.ArchivedPackage, @NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.content.IntentSender); method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException; method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback); method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler); @@ -12601,6 +12637,7 @@ package android.content.pm { method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo); method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo); method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @Nullable public android.content.pm.ArchivedPackage getArchivedPackage(@NonNull String); method @NonNull public CharSequence getBackgroundPermissionOptionLabel(); method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int); method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); @@ -12852,7 +12889,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access"; field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription"; field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television"; - field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; + field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch"; field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct"; @@ -17676,12 +17713,14 @@ package android.graphics.text { field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff + field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_STYLE_AUTO = 5; // 0x5 field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4 field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff + field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; // 0x2 field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff @@ -32404,7 +32443,7 @@ package android.os { method public void addData(@NonNull String, @Nullable byte[], int); method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException; method public void addText(@NonNull String, @NonNull String); - method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_DROPBOX_DATA, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); method public boolean isTagEnabled(String); field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED"; field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT"; @@ -33011,7 +33050,7 @@ package android.os { public static class PerformanceHintManager.Session implements java.io.Closeable { method public void close(); method public void reportActualWorkDuration(long); - method public void setPreferPowerEfficiency(boolean); + method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean); method public void setThreads(@NonNull int[]); method public void updateTargetWorkDuration(long); } @@ -33475,7 +33514,7 @@ package android.os { field public static final String DISALLOW_MICROPHONE_TOGGLE = "disallow_microphone_toggle"; field public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; field public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; - field public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio"; + field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio"; field public static final String DISALLOW_NETWORK_RESET = "no_network_reset"; field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; @@ -43127,6 +43166,7 @@ package android.telephony { field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.hide_roaming_icon") public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool"; field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; @@ -44464,7 +44504,7 @@ package android.telephony { method public static final boolean isStartsPostDial(char); method public static boolean isVoiceMailNumber(String); method public static boolean isWellFormedSmsAddress(String); - method public static boolean isWpsCallNumber(@Nullable String); + method @FlaggedApi("com.android.internal.telephony.flags.enable_wps_check_api_flag") public static boolean isWpsCallNumber(@NonNull String); method public static byte[] networkPortionToCalledPartyBCD(String); method public static byte[] networkPortionToCalledPartyBCDWithLength(String); method public static String normalizeNumber(String); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 2af3c34bf2c4..5bbff0b0b52f 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -120,7 +120,6 @@ package android.content { method @NonNull public android.os.IBinder getProcessToken(); method @NonNull public android.os.UserHandle getUser(); field public static final String PAC_PROXY_SERVICE = "pac_proxy"; - field public static final String REMOTE_AUTH_SERVICE = "remote_auth"; field public static final String TEST_NETWORK_SERVICE = "test_network"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1a7810adc5b4..119e0ad4e3da 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -315,6 +315,7 @@ package android { 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 @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final String RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT = "android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; @@ -2451,6 +2452,7 @@ package android.app.smartspace { method public int getFeatureType(); method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction(); method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid(); + method @FlaggedApi("android.app.smartspace.flags.remote_views") @Nullable public android.widget.RemoteViews getRemoteViews(); method public float getScore(); method @Nullable public android.net.Uri getSliceUri(); method @NonNull public String getSmartspaceTargetId(); @@ -2525,6 +2527,7 @@ package android.app.smartspace { method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(int); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>); + method @FlaggedApi("android.app.smartspace.flags.remote_views") @NonNull public android.app.smartspace.SmartspaceTarget.Builder setRemoteViews(@NonNull android.widget.RemoteViews); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(float); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(boolean); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean); @@ -3486,7 +3489,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; - field public static final String THREAD_NETWORK_SERVICE = "thread_network"; + field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_SERVICE = "thread_network"; field public static final String TIME_MANAGER_SERVICE = "time_manager"; field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; field public static final String UI_TRANSLATION_SERVICE = "ui_translation"; @@ -5583,7 +5586,8 @@ package android.hardware.radio { method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); - method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback); method public void removeOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); @@ -5637,12 +5641,13 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf field public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; // 0x2714 field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 - field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd - field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 @@ -5657,6 +5662,14 @@ package android.hardware.radio { field @Deprecated public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_1 = 1; // 0x1 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_2 = 2; // 0x2 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_3 = 4; // 0x4 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_4 = 8; // 0x8 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_5 = 16; // 0x10 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_6 = 32; // 0x20 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_7 = 64; // 0x40 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_8 = 128; // 0x80 } public static final class ProgramSelector.Identifier implements android.os.Parcelable { @@ -5669,7 +5682,7 @@ package android.hardware.radio { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR; } - @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { + @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { } @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType { @@ -5693,7 +5706,9 @@ package android.hardware.radio { field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8 field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7 field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9 - field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3 field public static final int CONFIG_FORCE_MONO = 1; // 0x1 field public static final int CONFIG_RDS_AF = 4; // 0x4 @@ -5821,8 +5836,11 @@ package android.hardware.radio { method @Deprecated public int getSubChannel(); method @NonNull public java.util.Map<java.lang.String,java.lang.String> getVendorInfo(); method @Deprecated public boolean isDigital(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdAudioAvailable(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdSisAvailable(); method public boolean isLive(); method public boolean isMuted(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isSignalAcquired(); method public boolean isStereo(); method public boolean isTrafficAnnouncementActive(); method public boolean isTrafficProgram(); @@ -5838,6 +5856,7 @@ package android.hardware.radio { method public android.hardware.radio.RadioMetadata.Clock getClock(String); method public int getInt(String); method public String getString(String); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public String[] getStringArray(@NonNull String); method public java.util.Set<java.lang.String> keySet(); method public int size(); method public void writeToParcel(android.os.Parcel, int); @@ -5846,6 +5865,9 @@ package android.hardware.radio { field public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART"; field public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST"; field public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMERCIAL = "android.hardware.radio.metadata.COMMERCIAL"; field public static final String METADATA_KEY_DAB_COMPONENT_NAME = "android.hardware.radio.metadata.DAB_COMPONENT_NAME"; field public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; field public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME"; @@ -5853,6 +5875,9 @@ package android.hardware.radio { field public static final String METADATA_KEY_DAB_SERVICE_NAME = "android.hardware.radio.metadata.DAB_SERVICE_NAME"; field public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT"; field public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_LONG = "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_SHORT = "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; field public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON"; field public static final String METADATA_KEY_PROGRAM_NAME = "android.hardware.radio.metadata.PROGRAM_NAME"; field public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY"; @@ -5861,6 +5886,7 @@ package android.hardware.radio { field public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY"; field public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT"; field public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; } public static final class RadioMetadata.Builder { @@ -5871,6 +5897,7 @@ package android.hardware.radio { method public android.hardware.radio.RadioMetadata.Builder putClock(String, long, int); method public android.hardware.radio.RadioMetadata.Builder putInt(String, int); method public android.hardware.radio.RadioMetadata.Builder putString(String, String); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public android.hardware.radio.RadioMetadata.Builder putStringArray(@NonNull String, @NonNull String[]); } public static final class RadioMetadata.Clock implements android.os.Parcelable { @@ -12688,6 +12715,7 @@ package android.service.voice { public static final class HotwordDetectionService.Callback { method public void onDetected(@NonNull android.service.voice.HotwordDetectedResult); method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult); + method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public void onTrainingData(@NonNull android.service.voice.HotwordTrainingData); } public final class HotwordDetectionServiceFailure implements android.os.Parcelable { @@ -12703,6 +12731,8 @@ package android.service.voice { field public static final int ERROR_CODE_DETECT_TIMEOUT = 4; // 0x4 field public static final int ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION = 5; // 0x5 field public static final int ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE = 6; // 0x6 + field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8; // 0x8 + field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9; // 0x9 field public static final int ERROR_CODE_REMOTE_EXCEPTION = 7; // 0x7 field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0 } @@ -12724,6 +12754,7 @@ package android.service.voice { method public void onRecognitionPaused(); method public void onRecognitionResumed(); method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult); + method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public default void onTrainingData(@NonNull android.service.voice.HotwordTrainingData); method public default void onUnknownFailure(@NonNull String); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 779777c7658a..249ca4dc364b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -872,7 +872,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String); ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); - ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); + ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -3047,6 +3047,7 @@ package android.service.voice { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties(); + method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT) public final void resetHotwordTrainingDataEgressCountForTest(); method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean); } @@ -3490,10 +3491,6 @@ package android.view { method public boolean isSystemGroup(); } - public abstract class LayoutInflater { - method public void setPrecompiledLayoutsEnabledForTesting(boolean); - } - public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { method public int getDisplayId(); method public void setActionButton(int); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 21ed098f448a..fd308ce2e85a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -48,6 +48,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApkChecksum; import android.content.pm.ApplicationInfo; +import android.content.pm.ArchivedPackage; import android.content.pm.ChangedPackages; import android.content.pm.Checksum; import android.content.pm.ComponentInfo; @@ -3936,6 +3937,19 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public @Nullable ArchivedPackage getArchivedPackage(@NonNull String packageName) { + try { + var parcel = mPM.getArchivedPackage(packageName, mContext.getUserId()); + if (parcel == null) { + return null; + } + return new ArchivedPackage(parcel); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override public boolean canUserUninstall(String packageName, UserHandle user) { try { return mPM.getBlockUninstallForUser(packageName, user.getIdentifier()); diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 37111e931d71..c8317c8faad5 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -27,7 +27,15 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArrayMap; - +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -608,6 +616,103 @@ public final class ApplicationStartInfo implements Parcelable { } }; + /** + * Write to a protocol buffer output stream. Protocol buffer message definition at {@link + * android.app.ApplicationStartInfoProto} + * + * @param proto Stream to write the ApplicationStartInfo object to. + * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message + * @hide + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { + final long token = proto.start(fieldId); + proto.write(ApplicationStartInfoProto.PID, mPid); + proto.write(ApplicationStartInfoProto.REAL_UID, mRealUid); + proto.write(ApplicationStartInfoProto.PACKAGE_UID, mPackageUid); + proto.write(ApplicationStartInfoProto.DEFINING_UID, mDefiningUid); + proto.write(ApplicationStartInfoProto.PROCESS_NAME, mProcessName); + proto.write(ApplicationStartInfoProto.STARTUP_STATE, mStartupState); + proto.write(ApplicationStartInfoProto.REASON, mReason); + if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) { + ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream(); + ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes); + timestampsOut.writeObject(mStartupTimestampsNs); + proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS, + timestampsBytes.toByteArray()); + } + proto.write(ApplicationStartInfoProto.START_TYPE, mStartType); + if (mStartIntent != null) { + Parcel parcel = Parcel.obtain(); + mStartIntent.writeToParcel(parcel, 0); + proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall()); + parcel.recycle(); + } + proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); + proto.end(token); + } + + /** + * Read from a protocol buffer input stream. Protocol buffer message definition at {@link + * android.app.ApplicationStartInfoProto} + * + * @param proto Stream to read the ApplicationStartInfo object from. + * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message + * @hide + */ + public void readFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + final long token = proto.start(fieldId); + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) ApplicationStartInfoProto.PID: + mPid = proto.readInt(ApplicationStartInfoProto.PID); + break; + case (int) ApplicationStartInfoProto.REAL_UID: + mRealUid = proto.readInt(ApplicationStartInfoProto.REAL_UID); + break; + case (int) ApplicationStartInfoProto.PACKAGE_UID: + mPackageUid = proto.readInt(ApplicationStartInfoProto.PACKAGE_UID); + break; + case (int) ApplicationStartInfoProto.DEFINING_UID: + mDefiningUid = proto.readInt(ApplicationStartInfoProto.DEFINING_UID); + break; + case (int) ApplicationStartInfoProto.PROCESS_NAME: + mProcessName = intern(proto.readString(ApplicationStartInfoProto.PROCESS_NAME)); + break; + case (int) ApplicationStartInfoProto.STARTUP_STATE: + mStartupState = proto.readInt(ApplicationStartInfoProto.STARTUP_STATE); + break; + case (int) ApplicationStartInfoProto.REASON: + mReason = proto.readInt(ApplicationStartInfoProto.REASON); + break; + case (int) ApplicationStartInfoProto.STARTUP_TIMESTAMPS: + ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes( + ApplicationStartInfoProto.STARTUP_TIMESTAMPS)); + ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes); + mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject(); + break; + case (int) ApplicationStartInfoProto.START_TYPE: + mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE); + break; + case (int) ApplicationStartInfoProto.START_INTENT: + byte[] startIntentBytes = proto.readBytes( + ApplicationStartInfoProto.START_INTENT); + if (startIntentBytes.length > 0) { + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length); + parcel.setDataPosition(0); + mStartIntent = Intent.CREATOR.createFromParcel(parcel); + parcel.recycle(); + } + break; + case (int) ApplicationStartInfoProto.LAUNCH_MODE: + mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); + break; + } + } + proto.end(token); + } + /** @hide */ public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix, @NonNull SimpleDateFormat sdf) { diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index e2082f754abc..180725efe0ae 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -706,8 +706,12 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } private boolean allowOverlappingTransitions() { - return mIsReturning ? getWindow().getAllowReturnTransitionOverlap() - : getWindow().getAllowEnterTransitionOverlap(); + final Window window = getWindow(); + if (window == null) { + return false; + } + return mIsReturning ? window.getAllowReturnTransitionOverlap() + : window.getAllowEnterTransitionOverlap(); } private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 94e1292a7554..3bde39c03f25 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5607,7 +5607,8 @@ public class Notification implements Parcelable // Use different highlighted colors for conversations' unread count if (p.mHighlightExpander) { pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); - textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor); + textColor = Colors.flattenAlpha( + getColors(p).getOnTertiaryAccentTextColor(), pillColor); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); @@ -12833,7 +12834,7 @@ public class Notification implements Parcelable private int mPrimaryAccentColor = COLOR_INVALID; private int mSecondaryAccentColor = COLOR_INVALID; private int mTertiaryAccentColor = COLOR_INVALID; - private int mOnAccentTextColor = COLOR_INVALID; + private int mOnTertiaryAccentTextColor = COLOR_INVALID; private int mErrorColor = COLOR_INVALID; private int mContrastColor = COLOR_INVALID; private int mRippleAlpha = 0x33; @@ -12908,7 +12909,7 @@ public class Notification implements Parcelable mPrimaryAccentColor = mPrimaryTextColor; mSecondaryAccentColor = mSecondaryTextColor; mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); - mOnAccentTextColor = mBackgroundColor; + mOnTertiaryAccentTextColor = mBackgroundColor; mErrorColor = mPrimaryTextColor; mRippleAlpha = 0x33; } else { @@ -12930,7 +12931,7 @@ public class Notification implements Parcelable mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID); mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID); mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID); - mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID); + mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID); mErrorColor = getColor(ta, 7, COLOR_INVALID); mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff)); } @@ -12955,8 +12956,8 @@ public class Notification implements Parcelable if (mTertiaryAccentColor == COLOR_INVALID) { mTertiaryAccentColor = mContrastColor; } - if (mOnAccentTextColor == COLOR_INVALID) { - mOnAccentTextColor = ColorUtils.setAlphaComponent( + if (mOnTertiaryAccentTextColor == COLOR_INVALID) { + mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent( ContrastColorUtil.resolvePrimaryColor( ctx, mTertiaryAccentColor, nightMode), 0xFF); } @@ -13029,8 +13030,8 @@ public class Notification implements Parcelable } /** @return the theme's text color to be used on the tertiary accent color. */ - public @ColorInt int getOnAccentTextColor() { - return mOnAccentTextColor; + public @ColorInt int getOnTertiaryAccentTextColor() { + return mOnTertiaryAccentTextColor; } /** diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index e2aee839b0c3..7418c06a2dd1 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -20,46 +20,31 @@ import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag; import static java.util.Objects.requireNonNull; -import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.app.ActivityThread; +import android.hardware.display.DisplayManagerGlobal; import android.os.Process; -import android.util.ArrayMap; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; -import java.util.concurrent.Executor; -import java.util.function.IntConsumer; - /** * Singleton controller to manage listeners to individual {@link ClientTransaction}. * - * TODO(b/260873529) make as TestApi to allow CTS. * @hide */ public class ClientTransactionListenerController { private static ClientTransactionListenerController sController; - private final Object mLock = new Object(); - - /** - * Mapping from client registered listener for display change to the corresponding - * {@link Executor} to invoke the listener on. - * @see #registerDisplayChangeListener(IntConsumer, Executor) - */ - @GuardedBy("mLock") - private final ArrayMap<IntConsumer, Executor> mDisplayChangeListeners = new ArrayMap<>(); - - private final ArrayList<IntConsumer> mTmpDisplayChangeListeners = new ArrayList<>(); + private final DisplayManagerGlobal mDisplayManager; /** Gets the singleton controller. */ @NonNull public static ClientTransactionListenerController getInstance() { synchronized (ClientTransactionListenerController.class) { if (sController == null) { - sController = new ClientTransactionListenerController(); + sController = new ClientTransactionListenerController( + DisplayManagerGlobal.getInstance()); } return sController; } @@ -68,45 +53,13 @@ public class ClientTransactionListenerController { /** Creates a new instance for test only. */ @VisibleForTesting @NonNull - public static ClientTransactionListenerController createInstanceForTesting() { - return new ClientTransactionListenerController(); + public static ClientTransactionListenerController createInstanceForTesting( + @NonNull DisplayManagerGlobal displayManager) { + return new ClientTransactionListenerController(displayManager); } - private ClientTransactionListenerController() {} - - /** - * Registers a new listener for display change. It will be invoked when receives a - * {@link ClientTransaction} that is updating display-related window configuration, such as - * bounds and rotation. - * - * WHen triggered, the listener will be invoked with the logical display id that was changed. - * - * @param listener the listener to invoke when receives a transaction with Display change. - * @param executor the executor on which callback method will be invoked. - */ - public void registerDisplayChangeListener(@NonNull IntConsumer listener, - @NonNull @CallbackExecutor Executor executor) { - if (!isSyncWindowConfigUpdateFlagEnabled()) { - return; - } - requireNonNull(listener); - requireNonNull(executor); - synchronized (mLock) { - mDisplayChangeListeners.put(listener, executor); - } - } - - /** - * Unregisters the listener for display change that was previously registered through - * {@link #registerDisplayChangeListener}. - */ - public void unregisterDisplayChangeListener(@NonNull IntConsumer listener) { - if (!isSyncWindowConfigUpdateFlagEnabled()) { - return; - } - synchronized (mLock) { - mDisplayChangeListeners.remove(listener); - } + private ClientTransactionListenerController(@NonNull DisplayManagerGlobal displayManager) { + mDisplayManager = requireNonNull(displayManager); } /** @@ -117,24 +70,14 @@ public class ClientTransactionListenerController { if (!isSyncWindowConfigUpdateFlagEnabled()) { return; } - synchronized (mLock) { - // Make a copy of the list to avoid listener removal during callback. - mTmpDisplayChangeListeners.addAll(mDisplayChangeListeners.keySet()); - final int num = mTmpDisplayChangeListeners.size(); - try { - for (int i = 0; i < num; i++) { - final IntConsumer listener = mTmpDisplayChangeListeners.get(i); - final Executor executor = mDisplayChangeListeners.get(listener); - executor.execute(() -> listener.accept(displayId)); - } - } finally { - mTmpDisplayChangeListeners.clear(); - } + if (ActivityThread.isSystem()) { + // Not enable for system server. + return; } + mDisplayManager.handleDisplayChangeFromWindowManager(displayId); } /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */ - @VisibleForTesting public boolean isSyncWindowConfigUpdateFlagEnabled() { // Can't read flag from isolated process. return !Process.isIsolated() && syncWindowConfigUpdateFlag(); diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java index 3c66a15399d3..f6f65c73dfb9 100644 --- a/core/java/android/app/smartspace/SmartspaceTarget.java +++ b/core/java/android/app/smartspace/SmartspaceTarget.java @@ -16,10 +16,12 @@ package android.app.smartspace; import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.smartspace.flags.Flags; import android.app.smartspace.uitemplatedata.BaseTemplateData; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -27,6 +29,7 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.widget.RemoteViews; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -41,7 +44,8 @@ import java.util.Objects; * {@link SmartspaceAction} as their type because they can have associated actions. * * <p><b>NOTE: </b> - * If {@link mWidget} is set, it should be preferred over all other properties. + * If either {@link mRemoteViews} or {@link mWidget} is set, it should be preferred over all + * other properties. (An exception is thrown if both are set.) * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties. * Otherwise, the instance should be treated as a data object. * @@ -133,6 +137,9 @@ public final class SmartspaceTarget implements Parcelable { private final AppWidgetProviderInfo mWidget; @Nullable + private final RemoteViews mRemoteViews; + + @Nullable private final BaseTemplateData mTemplateData; public static final int FEATURE_UNDEFINED = 0; @@ -288,6 +295,7 @@ public final class SmartspaceTarget implements Parcelable { this.mSliceUri = in.readTypedObject(Uri.CREATOR); this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR); this.mTemplateData = in.readParcelable(/* loader= */null, BaseTemplateData.class); + this.mRemoteViews = in.readTypedObject(RemoteViews.CREATOR); } private SmartspaceTarget(String smartspaceTargetId, @@ -298,7 +306,7 @@ public final class SmartspaceTarget implements Parcelable { boolean shouldShowExpanded, String sourceNotificationKey, ComponentName componentName, UserHandle userHandle, String associatedSmartspaceTargetId, Uri sliceUri, - AppWidgetProviderInfo widget, BaseTemplateData templateData) { + AppWidgetProviderInfo widget, BaseTemplateData templateData, RemoteViews remoteViews) { mSmartspaceTargetId = smartspaceTargetId; mHeaderAction = headerAction; mBaseAction = baseAction; @@ -317,6 +325,7 @@ public final class SmartspaceTarget implements Parcelable { mSliceUri = sliceUri; mWidget = widget; mTemplateData = templateData; + mRemoteViews = remoteViews; } /** @@ -461,6 +470,15 @@ public final class SmartspaceTarget implements Parcelable { } /** + * Returns the {@link RemoteViews} to show over the target. + */ + @FlaggedApi(Flags.FLAG_REMOTE_VIEWS) + @Nullable + public RemoteViews getRemoteViews() { + return mRemoteViews; + } + + /** * @see Parcelable.Creator */ @NonNull @@ -496,6 +514,7 @@ public final class SmartspaceTarget implements Parcelable { dest.writeTypedObject(this.mSliceUri, flags); dest.writeTypedObject(this.mWidget, flags); dest.writeParcelable(this.mTemplateData, flags); + dest.writeTypedObject(this.mRemoteViews, flags); } @Override @@ -524,6 +543,7 @@ public final class SmartspaceTarget implements Parcelable { + ", mSliceUri=" + mSliceUri + ", mWidget=" + mWidget + ", mTemplateData=" + mTemplateData + + ", mRemoteViews=" + mRemoteViews + '}'; } @@ -550,7 +570,8 @@ public final class SmartspaceTarget implements Parcelable { that.mAssociatedSmartspaceTargetId) && Objects.equals(mSliceUri, that.mSliceUri) && Objects.equals(mWidget, that.mWidget) - && Objects.equals(mTemplateData, that.mTemplateData); + && Objects.equals(mTemplateData, that.mTemplateData) + && Objects.equals(mRemoteViews, that.mRemoteViews); } @Override @@ -558,7 +579,7 @@ public final class SmartspaceTarget implements Parcelable { return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle, - mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData); + mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData, mRemoteViews); } /** @@ -588,6 +609,8 @@ public final class SmartspaceTarget implements Parcelable { private AppWidgetProviderInfo mWidget; private BaseTemplateData mTemplateData; + private RemoteViews mRemoteViews; + /** * A builder for {@link SmartspaceTarget}. * @@ -727,9 +750,15 @@ public final class SmartspaceTarget implements Parcelable { * * <p><b>NOTE: </b> If {@link mWidget} is set, all other @Nullable params should be * ignored. + * + * @throws An {@link IllegalStateException} is thrown if {@link mRemoteViews} is set. */ @NonNull public Builder setWidget(@NonNull AppWidgetProviderInfo widget) { + if (mRemoteViews != null) { + throw new IllegalStateException( + "Widget providers and RemoteViews cannot be used at the same time."); + } this.mWidget = widget; return this; } @@ -745,6 +774,25 @@ public final class SmartspaceTarget implements Parcelable { } /** + * Sets the {@link RemoteViews}. + * + * <p><b>NOTE: </b> If {@link RemoteViews} is set, all other @Nullable params should be + * ignored. + * + * @throws An {@link IllegalStateException} is thrown if {@link mWidget} is set. + */ + @FlaggedApi(Flags.FLAG_REMOTE_VIEWS) + @NonNull + public Builder setRemoteViews(@NonNull RemoteViews remoteViews) { + if (mWidget != null) { + throw new IllegalStateException( + "Widget providers and RemoteViews cannot be used at the same time."); + } + mRemoteViews = remoteViews; + return this; + } + + /** * Builds a new {@link SmartspaceTarget}. * * @throws IllegalStateException when non null fields are set as null. @@ -760,7 +808,7 @@ public final class SmartspaceTarget implements Parcelable { mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle, - mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData); + mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData, mRemoteViews); } } } diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig new file mode 100644 index 000000000000..6aefa38ac18c --- /dev/null +++ b/core/java/android/app/smartspace/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.app.smartspace.flags" + +flag { + name: "remote_views" + namespace: "sysui_integrations" + description: "Flag to enable the FlaggedApi to include RemoteViews in SmartspaceTarget" + bug: "300157758" +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/app/usage/ParcelableUsageEventList.aidl index 6d7c57605bec..165299651cff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt +++ b/core/java/android/app/usage/ParcelableUsageEventList.aidl @@ -5,7 +5,7 @@ * 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 + * 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, @@ -14,9 +14,6 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.viewmodel +package android.app.usage; -enum class QSTileLifecycle { - ALIVE, - DEAD, -} +parcelable ParcelableUsageEventList; diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java new file mode 100644 index 000000000000..016d97f60e26 --- /dev/null +++ b/core/java/android/app/usage/ParcelableUsageEventList.java @@ -0,0 +1,266 @@ +/* + * 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.usage; + +import android.annotation.NonNull; +import android.app.usage.UsageEvents.Event; +import android.content.res.Configuration; +import android.os.BadParcelableException; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a copied version of BaseParceledListSlice with specific + * {@link UsageEvents.Event} instance that used to transfer the large + * list of {@link UsageEvents.Event} objects across an IPC. Splits + * into multiple transactions if needed. + * + * @see BasedParceledListSlice + * + * @hide + */ +public final class ParcelableUsageEventList implements Parcelable { + private static final String TAG = "ParcelableUsageEventList"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_ALL = false; + + private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); + + private List<Event> mList; + + public ParcelableUsageEventList(List<Event> list) { + mList = list; + } + + private ParcelableUsageEventList(Parcel in) { + final int N = in.readInt(); + mList = new ArrayList<>(); + if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); + if (N <= 0) { + return; + } + + int i = 0; + while (i < N) { + if (in.readInt() == 0) { + break; + } + mList.add(readEventFromParcel(in)); + if (DEBUG_ALL) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1)); + i++; + } + if (DEBUG) { + Log.d(TAG, "Read " + mList.size() + " inline UsageEvents" + + ", total N=" + N + " UsageEvents"); + } + if (i >= N) { + return; + } + final IBinder retriever = in.readStrongBinder(); + while (i < N) { + if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInt(i); + try { + retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); + reply.readException(); + int count = 0; + while (i < N && reply.readInt() != 0) { + mList.add(readEventFromParcel(reply)); + if (DEBUG_ALL) { + Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1)); + } + i++; + count++; + } + if (DEBUG) Log.d(TAG, "Read extra @" + count + " of " + N); + } catch (RemoteException e) { + throw new BadParcelableException( + "Failure retrieving array; only received " + i + " of " + N, e); + } finally { + reply.recycle(); + data.recycle(); + } + } + if (DEBUG) Log.d(TAG, "Finish reading total " + i + " UsageEvents"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + final int N = mList.size(); + final int callFlags = flags; + dest.writeInt(N); + if (DEBUG) Log.d(TAG, "Writing " + N + " items"); + if (N > 0) { + int i = 0; + while (i < N && dest.dataSize() < MAX_IPC_SIZE) { + dest.writeInt(1); + + final Event event = mList.get(i); + writeEventToParcel(event, dest, callFlags); + + if (DEBUG_ALL) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); + i++; + } + if (i < N) { + dest.writeInt(0); + Binder retriever = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code != FIRST_CALL_TRANSACTION) { + return super.onTransact(code, data, reply, flags); + } else if (mList == null) { + throw new IllegalArgumentException("Attempt to transfer null list, " + + "did transfer finish?"); + } + int i = data.readInt(); + + if (DEBUG) { + Log.d(TAG, "Writing more @" + i + " of " + N + " to " + + Binder.getCallingPid() + ", sender=" + this); + } + + try { + reply.writeNoException(); + int count = 0; + while (i < N && reply.dataSize() < MAX_IPC_SIZE) { + reply.writeInt(1); + + final Event event = mList.get(i); + writeEventToParcel(event, reply, callFlags); + + if (DEBUG_ALL) { + Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); + } + i++; + count++; + } + if (i < N) { + if (DEBUG) { + Log.d(TAG, "Breaking @" + i + " of " + N + + "(count = " + count + ")"); + } + reply.writeInt(0); + } else { + if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference"); + mList = null; + } + } catch (RuntimeException e) { + if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference"); + mList = null; + throw e; + } + return true; + } + }; + if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); + dest.writeStrongBinder(retriever); + } + } + } + + public List<Event> getList() { + return mList; + } + + public static final Parcelable.Creator<ParcelableUsageEventList> CREATOR = + new Parcelable.Creator<ParcelableUsageEventList>() { + public ParcelableUsageEventList createFromParcel(Parcel in) { + return new ParcelableUsageEventList(in); + } + + @Override + public ParcelableUsageEventList[] newArray(int size) { + return new ParcelableUsageEventList[size]; + } + }; + + private Event readEventFromParcel(Parcel in) { + final Event event = new Event(); + event.mPackage = in.readString(); + event.mClass = in.readString(); + event.mInstanceId = in.readInt(); + event.mTaskRootPackage = in.readString(); + event.mTaskRootClass = in.readString(); + event.mEventType = in.readInt(); + event.mTimeStamp = in.readLong(); + + // Fill out the event-dependant fields. + event.mConfiguration = null; + event.mShortcutId = null; + event.mAction = null; + event.mContentType = null; + event.mContentAnnotations = null; + event.mNotificationChannelId = null; + event.mLocusId = null; + + switch (event.mEventType) { + case Event.CONFIGURATION_CHANGE -> { + event.mConfiguration = Configuration.CREATOR.createFromParcel(in); + } + case Event.SHORTCUT_INVOCATION -> event.mShortcutId = in.readString(); + case Event.CHOOSER_ACTION -> { + event.mAction = in.readString(); + event.mContentType = in.readString(); + event.mContentAnnotations = in.readStringArray(); + } + case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt(); + case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString(); + case Event.LOCUS_ID_SET -> event.mLocusId = in.readString(); + } + event.mFlags = in.readInt(); + + return event; + } + + private void writeEventToParcel(@NonNull Event event, @NonNull Parcel dest, int flags) { + dest.writeString(event.mPackage); + dest.writeString(event.mClass); + dest.writeInt(event.mInstanceId); + dest.writeString(event.mTaskRootPackage); + dest.writeString(event.mTaskRootClass); + dest.writeInt(event.mEventType); + dest.writeLong(event.mTimeStamp); + + switch (event.mEventType) { + case Event.CONFIGURATION_CHANGE -> event.mConfiguration.writeToParcel(dest, flags); + case Event.SHORTCUT_INVOCATION -> dest.writeString(event.mShortcutId); + case Event.CHOOSER_ACTION -> { + dest.writeString(event.mAction); + dest.writeString(event.mContentType); + dest.writeStringArray(event.mContentAnnotations); + } + case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason); + case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId); + case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId); + } + dest.writeInt(event.mFlags); + } +} diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 3c256ad97f21..c188686d2d5b 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -714,7 +714,7 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage private Parcel mParcel = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private final int mEventCount; + private int mEventCount; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mIndex = 0; @@ -735,6 +735,23 @@ public final class UsageEvents implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UsageEvents(Parcel in) { + if (Flags.useParceledList()) { + readUsageEventsFromParcelWithParceledList(in); + } else { + readUsageEventsFromParcelWithBlob(in); + } + + mIncludeTaskRoots = true; + } + + private void readUsageEventsFromParcelWithParceledList(Parcel in) { + mIndex = in.readInt(); + mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(), + ParcelableUsageEventList.class).getList(); + mEventCount = mEventsToWrite.size(); + } + + private void readUsageEventsFromParcelWithBlob(Parcel in) { byte[] bytes = in.readBlob(); Parcel data = Parcel.obtain(); data.unmarshall(bytes, 0, bytes.length); @@ -752,7 +769,6 @@ public final class UsageEvents implements Parcelable { mParcel.setDataSize(mParcel.dataPosition()); mParcel.setDataPosition(positionInParcel); } - mIncludeTaskRoots = true; } /** @@ -810,6 +826,10 @@ public final class UsageEvents implements Parcelable { return false; } + if (Flags.useParceledList()) { + return getNextEventFromParceledList(eventOut); + } + if (mParcel != null) { readEventFromParcel(mParcel, eventOut); } else { @@ -824,6 +844,12 @@ public final class UsageEvents implements Parcelable { return true; } + private boolean getNextEventFromParceledList(Event eventOut) { + eventOut.copyFrom(mEventsToWrite.get(mIndex)); + mIndex++; + return true; + } + /** * Resets the collection so that it can be iterated over from the beginning. * @@ -968,7 +994,7 @@ public final class UsageEvents implements Parcelable { case Event.CHOOSER_ACTION: eventOut.mAction = p.readString(); eventOut.mContentType = p.readString(); - eventOut.mContentAnnotations = p.createStringArray(); + eventOut.mContentAnnotations = p.readStringArray(); break; case Event.STANDBY_BUCKET_CHANGED: eventOut.mBucketAndReason = p.readInt(); @@ -990,6 +1016,19 @@ public final class UsageEvents implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + if (Flags.useParceledList()) { + writeUsageEventsToParcelWithParceledList(dest, flags); + } else { + writeUsageEventsToParcelWithBlob(dest, flags); + } + } + + private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) { + dest.writeInt(mIndex); + dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags); + } + + private void writeUsageEventsToParcelWithBlob(Parcel dest, int flags) { Parcel data = Parcel.obtain(); data.writeInt(mEventCount); data.writeInt(mIndex); diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index 4f1c65b1e395..0b8e29f954a5 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -21,3 +21,10 @@ flag { is_fixed_read_only: true bug: "299336442" } + +flag { + name: "use_parceled_list" + namespace: "backstage_power" + description: "Flag for parcelable usage event list" + bug: "301254110" +} diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 570ecaa47b4e..c99a45764de7 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -17,6 +17,7 @@ package android.companion; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; @@ -140,24 +141,28 @@ public abstract class CompanionDeviceService extends Service { * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback * with this event if the device comes into BLE range. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_BLE_APPEARED = 0; /** * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback * with this event if the device is no longer in BLE range. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; /** * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback * with this event when the bluetooth device is connected. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_BT_CONNECTED = 2; /** * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback * with this event if the bluetooth device is disconnected. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; /** @@ -165,6 +170,7 @@ public abstract class CompanionDeviceService extends Service { * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its * own. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; /** @@ -172,6 +178,7 @@ public abstract class CompanionDeviceService extends Service { * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on * its own. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; private final Stub mRemote = new Stub(); @@ -348,6 +355,7 @@ public abstract class CompanionDeviceService extends Service { * @param associationInfo A record for the companion device. * @param event Associated companion device's event. */ + @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE) @MainThread public void onDeviceEvent(@NonNull AssociationInfo associationInfo, @DeviceEvent int event) { diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig index 4f9c849865fc..6e33dff3a379 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -19,4 +19,11 @@ flag { namespace: "companion" description: "Enable Association tag APIs " bug: "289241123" -}
\ No newline at end of file +} + +flag { + name: "device_presence" + namespace: "companion" + description: "Enable device presence APIs" + bug: "283000075" +} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 62fbcaff79e3..4b2cee698df2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -155,6 +155,7 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi + @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 59bb73b5916d..1c917ee335af 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -23,6 +23,7 @@ import android.annotation.ColorInt; import android.annotation.ColorRes; import android.annotation.DisplayContext; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; @@ -860,12 +861,23 @@ public abstract class Context { * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when * appropriate in the future; this will not be removed for you. * <p> - * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created + * After {@link Build.VERSION_CODES#S}, registering the ComponentCallbacks to Context created * via {@link #createWindowContext(int, Bundle)} or * {@link #createWindowContext(Display, int, Bundle)} will receive * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather * than its base application. It is helpful if you want to handle UI components that * associated with the Window Context when the Window Context has configuration changes.</p> + * <p> + * After {@link Build.VERSION_CODES#TIRAMISU}, registering the ComponentCallbacks to + * {@link Activity} context will receive + * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from + * {@link Activity#onConfigurationChanged(Configuration)} rather than its base application.</p> + * <p> + * After {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, registering the ComponentCallbacks to + * {@link android.inputmethodservice.InputMethodService} will receive + * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from InputmethodService + * rather than its base application. It is helpful if you want to handle UI components when the + * IME has configuration changes.</p> * * @param callback The interface to call. This can be either a * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface. @@ -4773,6 +4785,7 @@ public abstract class Context { * @see android.net.thread.ThreadNetworkManager * @hide */ + @FlaggedApi("com.android.net.thread.flags.thread_enabled") @SystemApi public static final String THREAD_NETWORK_SERVICE = "thread_network"; @@ -6370,7 +6383,6 @@ public abstract class Context { * @see android.remoteauth.RemoteAuthManager * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String REMOTE_AUTH_SERVICE = "remote_auth"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 7b6bad31539a..ffc4805df264 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6340,6 +6340,7 @@ public class Intent implements Parcelable, Cloneable { * the package is being archived. Either by removing the existing APK, or by installing * a package without an APK. */ + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final String EXTRA_ARCHIVAL = "android.intent.extra.ARCHIVAL"; /** diff --git a/core/java/android/content/pm/ArchivedActivity.java b/core/java/android/content/pm/ArchivedActivity.java new file mode 100644 index 000000000000..9e49c9e52878 --- /dev/null +++ b/core/java/android/content/pm/ArchivedActivity.java @@ -0,0 +1,245 @@ +/* + * 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.content.pm; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +import com.android.internal.util.DataClass; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Objects; + +@DataClass(genBuilder = false, genConstructor = false, genSetters = true) +@FlaggedApi(Flags.FLAG_ARCHIVING) +public final class ArchivedActivity { + /** The label for the activity. */ + private @NonNull CharSequence mLabel; + /** The component name of this activity. */ + private @NonNull ComponentName mComponentName; + /** + * Icon of the activity in the app's locale. if null then the default icon would be shown in the + * launcher. + */ + private @Nullable Drawable mIcon; + /** Monochrome icon, if defined, of the activity. */ + private @Nullable Drawable mMonochromeIcon; + + public ArchivedActivity(@NonNull CharSequence label, @NonNull ComponentName componentName) { + Objects.requireNonNull(label); + Objects.requireNonNull(componentName); + mLabel = label; + mComponentName = componentName; + } + + /* @hide */ + ArchivedActivity(@NonNull ArchivedActivityParcel parcel) { + mLabel = parcel.title; + mComponentName = parcel.originalComponentName; + mIcon = drawableFromCompressedBitmap(parcel.iconBitmap); + mMonochromeIcon = drawableFromCompressedBitmap(parcel.monochromeIconBitmap); + } + + /* @hide */ + @NonNull ArchivedActivityParcel getParcel() { + var parcel = new ArchivedActivityParcel(); + parcel.title = mLabel.toString(); + parcel.originalComponentName = mComponentName; + parcel.iconBitmap = mIcon == null ? null : + bytesFromBitmap(drawableToBitmap(mIcon)); + parcel.monochromeIconBitmap = mMonochromeIcon == null ? null : + bytesFromBitmap(drawableToBitmap(mMonochromeIcon)); + return parcel; + } + + /** + * Convert a generic drawable into a bitmap. + * @hide + */ + public static Bitmap drawableToBitmap(Drawable drawable) { + return drawableToBitmap(drawable, /* iconSize= */ 0); + } + + /** + * Same as above, but scale the resulting image to fit iconSize. + * @hide + */ + public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + + } + + Bitmap bitmap; + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + // Needed for drawables that are just a single color. + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = + Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) { + var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true); + if (scaledBitmap != bitmap) { + bitmap.recycle(); + } + return scaledBitmap; + } + return bitmap; + } + + /** + * Compress bitmap to PNG format. + * @hide + */ + public static byte[] bytesFromBitmap(Bitmap bitmap) { + if (bitmap == null) { + return null; + } + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream( + bitmap.getByteCount())) { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } catch (IOException ignored) { + return null; + } + } + + private static Drawable drawableFromCompressedBitmap(byte[] bytes) { + if (bytes == null) { + return null; + } + return new BitmapDrawable(null /*res*/, new ByteArrayInputStream(bytes)); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ArchivedActivity.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * The label for the activity. + */ + @DataClass.Generated.Member + public @NonNull CharSequence getLabel() { + return mLabel; + } + + /** + * The component name of this activity. + */ + @DataClass.Generated.Member + public @NonNull ComponentName getComponentName() { + return mComponentName; + } + + /** + * Icon of the activity in the app's locale. if null then the default icon would be shown in the + * launcher. + */ + @DataClass.Generated.Member + public @Nullable Drawable getIcon() { + return mIcon; + } + + /** + * Monochrome icon, if defined, of the activity. + */ + @DataClass.Generated.Member + public @Nullable Drawable getMonochromeIcon() { + return mMonochromeIcon; + } + + /** + * The label for the activity. + */ + @DataClass.Generated.Member + public @NonNull ArchivedActivity setLabel(@NonNull CharSequence value) { + mLabel = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mLabel); + return this; + } + + /** + * The component name of this activity. + */ + @DataClass.Generated.Member + public @NonNull ArchivedActivity setComponentName(@NonNull ComponentName value) { + mComponentName = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mComponentName); + return this; + } + + /** + * Icon of the activity in the app's locale. if null then the default icon would be shown in the + * launcher. + */ + @DataClass.Generated.Member + public @NonNull ArchivedActivity setIcon(@NonNull Drawable value) { + mIcon = value; + return this; + } + + /** + * Monochrome icon, if defined, of the activity. + */ + @DataClass.Generated.Member + public @NonNull ArchivedActivity setMonochromeIcon(@NonNull Drawable value) { + mMonochromeIcon = value; + return this; + } + + @DataClass.Generated( + time = 1698173429911L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivity.java", + inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivity extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/ArchivedPackage.java b/core/java/android/content/pm/ArchivedPackage.java new file mode 100644 index 000000000000..42795db35684 --- /dev/null +++ b/core/java/android/content/pm/ArchivedPackage.java @@ -0,0 +1,344 @@ +/* + * 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.content.pm; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@DataClass(genBuilder = false, genConstructor = false, genSetters = true) +@FlaggedApi(Flags.FLAG_ARCHIVING) +public final class ArchivedPackage { + /** Name of the package as used to identify it in the system */ + private @NonNull String mPackageName; + /** Signing certificates used to sign the package. */ + private @NonNull SigningInfo mSigningInfo; + /** + * The version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute. + */ + private int mVersionCode = 0; + /** + * The major version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute. + */ + private int mVersionCodeMajor = 0; + /** + * This is the SDK version number that the application is targeting, as specified by the + * <manifest> tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion} + * attribute. + */ + private int mTargetSdkVersion = 0; + /** + * Package data will default to device protected storage. Specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage} + * attribute. + */ + private @Nullable String mDefaultToDeviceProtectedStorage; + /** + * If {@code true} this app would like to run under the legacy storage model. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage} + * attribute. + */ + private @Nullable String mRequestLegacyExternalStorage; + /** + * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute. + */ + private @Nullable String mUserDataFragile; + /** + * List of the package's activities that specify {@link Intent#ACTION_MAIN} and + * {@link Intent#CATEGORY_LAUNCHER}. + * @see LauncherApps#getActivityList + */ + private @NonNull List<ArchivedActivity> mLauncherActivities; + + public ArchivedPackage(@NonNull String packageName, @NonNull SigningInfo signingInfo, + @NonNull List<ArchivedActivity> launcherActivities) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(signingInfo); + Objects.requireNonNull(launcherActivities); + this.mPackageName = packageName; + this.mSigningInfo = signingInfo; + this.mLauncherActivities = launcherActivities; + } + + /** + * Constructs the archived package from parcel. + * @hide + */ + public ArchivedPackage(@NonNull ArchivedPackageParcel parcel) { + mPackageName = parcel.packageName; + mSigningInfo = new SigningInfo(parcel.signingDetails); + mVersionCode = parcel.versionCode; + mVersionCodeMajor = parcel.versionCodeMajor; + mTargetSdkVersion = parcel.targetSdkVersion; + mDefaultToDeviceProtectedStorage = parcel.defaultToDeviceProtectedStorage; + mRequestLegacyExternalStorage = parcel.requestLegacyExternalStorage; + mUserDataFragile = parcel.userDataFragile; + mLauncherActivities = new ArrayList<>(); + if (parcel.archivedActivities != null) { + for (var activityParcel : parcel.archivedActivities) { + mLauncherActivities.add(new ArchivedActivity(activityParcel)); + } + } + } + + /* @hide */ + ArchivedPackageParcel getParcel() { + var parcel = new ArchivedPackageParcel(); + parcel.packageName = mPackageName; + parcel.signingDetails = mSigningInfo.getSigningDetails(); + parcel.versionCode = mVersionCode; + parcel.versionCodeMajor = mVersionCodeMajor; + parcel.targetSdkVersion = mTargetSdkVersion; + parcel.defaultToDeviceProtectedStorage = mDefaultToDeviceProtectedStorage; + parcel.requestLegacyExternalStorage = mRequestLegacyExternalStorage; + parcel.userDataFragile = mUserDataFragile; + + parcel.archivedActivities = new ArchivedActivityParcel[mLauncherActivities.size()]; + for (int i = 0, size = parcel.archivedActivities.length; i < size; ++i) { + parcel.archivedActivities[i] = mLauncherActivities.get(i).getParcel(); + } + + return parcel; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ArchivedPackage.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Name of the package as used to identify it in the system + */ + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * Signing certificates used to sign the package. + */ + @DataClass.Generated.Member + public @NonNull SigningInfo getSigningInfo() { + return mSigningInfo; + } + + /** + * The version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute. + */ + @DataClass.Generated.Member + public int getVersionCode() { + return mVersionCode; + } + + /** + * The major version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute. + */ + @DataClass.Generated.Member + public int getVersionCodeMajor() { + return mVersionCodeMajor; + } + + /** + * This is the SDK version number that the application is targeting, as specified by the + * <manifest> tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion} + * attribute. + */ + @DataClass.Generated.Member + public int getTargetSdkVersion() { + return mTargetSdkVersion; + } + + /** + * Package data will default to device protected storage. Specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage} + * attribute. + */ + @DataClass.Generated.Member + public @Nullable String getDefaultToDeviceProtectedStorage() { + return mDefaultToDeviceProtectedStorage; + } + + /** + * If {@code true} this app would like to run under the legacy storage model. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage} + * attribute. + */ + @DataClass.Generated.Member + public @Nullable String getRequestLegacyExternalStorage() { + return mRequestLegacyExternalStorage; + } + + /** + * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute. + */ + @DataClass.Generated.Member + public @Nullable String getUserDataFragile() { + return mUserDataFragile; + } + + /** + * List of the package's activities that specify {@link Intent#ACTION_MAIN} and + * {@link Intent#CATEGORY_LAUNCHER}. + * + * @see LauncherApps#getActivityList + */ + @DataClass.Generated.Member + public @NonNull List<ArchivedActivity> getLauncherActivities() { + return mLauncherActivities; + } + + /** + * Name of the package as used to identify it in the system + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setPackageName(@NonNull String value) { + mPackageName = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + return this; + } + + /** + * Signing certificates used to sign the package. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setSigningInfo(@NonNull SigningInfo value) { + mSigningInfo = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSigningInfo); + return this; + } + + /** + * The version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCode} attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setVersionCode( int value) { + mVersionCode = value; + return this; + } + + /** + * The major version number of the package, as specified by the <manifest>tag's + * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setVersionCodeMajor( int value) { + mVersionCodeMajor = value; + return this; + } + + /** + * This is the SDK version number that the application is targeting, as specified by the + * <manifest> tag's {@link android.R.styleable#AndroidManifestUsesSdk_targetSdkVersion} + * attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setTargetSdkVersion( int value) { + mTargetSdkVersion = value; + return this; + } + + /** + * Package data will default to device protected storage. Specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage} + * attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setDefaultToDeviceProtectedStorage(@NonNull String value) { + mDefaultToDeviceProtectedStorage = value; + return this; + } + + /** + * If {@code true} this app would like to run under the legacy storage model. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage} + * attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setRequestLegacyExternalStorage(@NonNull String value) { + mRequestLegacyExternalStorage = value; + return this; + } + + /** + * If {@code true} the user is prompted to keep the app's data on uninstall. Specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifestApplication_hasFragileUserData} attribute. + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setUserDataFragile(@NonNull String value) { + mUserDataFragile = value; + return this; + } + + /** + * List of the package's activities that specify {@link Intent#ACTION_MAIN} and + * {@link Intent#CATEGORY_LAUNCHER}. + * + * @see LauncherApps#getActivityList + */ + @DataClass.Generated.Member + public @NonNull ArchivedPackage setLauncherActivities(@NonNull List<ArchivedActivity> value) { + mLauncherActivities = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mLauncherActivities); + return this; + } + + @DataClass.Generated( + time = 1697824890503L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedPackage.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate int mVersionCode\nprivate int mVersionCodeMajor\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable java.lang.String mDefaultToDeviceProtectedStorage\nprivate @android.annotation.Nullable java.lang.String mRequestLegacyExternalStorage\nprivate @android.annotation.Nullable java.lang.String mUserDataFragile\nprivate @android.annotation.NonNull java.util.List<android.content.pm.ArchivedActivity> mLauncherActivities\n android.content.pm.ArchivedPackageParcel getParcel()\nclass ArchivedPackage extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index edb07ce423c1..59ed0453bc01 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -82,4 +83,11 @@ interface IPackageInstaller { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)") + void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel, + in PackageInstaller.SessionParams params, + in IntentSender statusReceiver, + String installerPackageName, in UserHandle userHandle); + } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d837aae35096..cd8938d1dd77 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1000,6 +1000,37 @@ public class PackageInstaller { } } + /** + * Install package in an archived state. + * + * @param archivedPackage archived package data such as package name, signature etc. + * @param sessionParams used to create an underlying installation session + * @param statusReceiver Called when the state of the session changes. Intents + * sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the + * individual status codes on how to handle them. + * @see #createSession + * @see PackageInstaller.Session#commit + */ + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) + @FlaggedApi(Flags.FLAG_ARCHIVING) + public void installPackageArchived(@NonNull ArchivedPackage archivedPackage, + @NonNull SessionParams sessionParams, + @NonNull IntentSender statusReceiver) { + Objects.requireNonNull(archivedPackage, "archivedPackage cannot be null"); + Objects.requireNonNull(sessionParams, "sessionParams cannot be null"); + Objects.requireNonNull(statusReceiver, "statusReceiver cannot be null"); + try { + mInstaller.installPackageArchived( + archivedPackage.getParcel(), + sessionParams, + statusReceiver, + mInstallerPackageName, + new UserHandle(mUserId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b15c9e4fa15b..ad7dd513b382 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3778,6 +3778,7 @@ public abstract class PackageManager { * The device is capable of communicating with other devices via * <a href="https://www.threadgroup.org">Thread</a> networking protocol. */ + @FlaggedApi("com.android.net.thread.flags.thread_enabled") @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; @@ -11026,6 +11027,16 @@ public abstract class PackageManager { "makeUidVisible not implemented in subclass"); } + /** + * Return archived package info for the package or null if the package is not installed. + * @see PackageInstaller#installPackageArchived + */ + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) + public @Nullable ArchivedPackage getArchivedPackage(@NonNull String packageName) { + throw new UnsupportedOperationException( + "getArchivedPackage not implemented in subclass"); + } + // Some of the flags don't affect the query result, but let's be conservative and cache // each combination of flags separately. diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java index ee9aaca3ed43..554de0c2ea7b 100644 --- a/core/java/android/content/pm/SigningInfo.java +++ b/core/java/android/content/pm/SigningInfo.java @@ -126,6 +126,12 @@ public final class SigningInfo implements Parcelable { mSigningDetails.writeToParcel(dest, parcelableFlags); } + /* @hide */ + @NonNull + SigningDetails getSigningDetails() { + return mSigningDetails; + } + public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR = new Parcelable.Creator<SigningInfo>() { @Override diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 9c05dfc94ad4..82694ee3463b 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -537,24 +537,6 @@ 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 8eede472bec5..c2e5c0b6d519 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -17,7 +17,6 @@ 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; @@ -64,9 +63,6 @@ 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/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 36606a135f3e..18c8d1bd3a1e 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,7 +17,6 @@ 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; @@ -69,10 +68,6 @@ 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/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index bf77681bbbbd..db7055b1756d 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -357,7 +357,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface(); } mRepeatingRequestImageCallback = new CameraOutputImageCallback( - mRepeatingRequestImageReader); + mRepeatingRequestImageReader, true /*pruneOlderBuffers*/); mRepeatingRequestImageReader .setOnImageAvailableListener(mRepeatingRequestImageCallback, mHandler); } @@ -398,7 +398,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT); } - mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader); + mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader, + false /*pruneOlderBuffers*/); mBurstCaptureImageReader.setOnImageAvailableListener(mBurstCaptureImageCallback, mHandler); mCameraBurstSurface = mBurstCaptureImageReader.getSurface(); @@ -1106,7 +1107,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) { - captureStage.first.close(); + if (captureStage.first != null) { + captureStage.first.close(); + } } mCaptureStageMap.clear(); } @@ -1207,6 +1210,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { if (mImageProcessor != null) { if (mCapturePendingMap.indexOfKey(timestamp) >= 0) { Image img = mCapturePendingMap.get(timestamp).first; + mCapturePendingMap.remove(timestamp); mCaptureStageMap.put(stageId, new Pair<>(img, result)); checkAndFireBurstProcessing(); } else { @@ -1303,6 +1307,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { reader.detachImage(img); if (mCapturePendingMap.indexOfKey(timestamp) >= 0) { Integer stageId = mCapturePendingMap.get(timestamp).second; + mCapturePendingMap.remove(timestamp); Pair<Image, TotalCaptureResult> captureStage = mCaptureStageMap.get(stageId); if (captureStage != null) { @@ -1402,9 +1407,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private HashMap<Long, Pair<Image, OnImageAvailableListener>> mImageListenerMap = new HashMap<>(); private boolean mOutOfBuffers = false; + private final boolean mPruneOlderBuffers; - CameraOutputImageCallback(ImageReader imageReader) { + CameraOutputImageCallback(ImageReader imageReader, boolean pruneOlderBuffers) { mImageReader = imageReader; + mPruneOlderBuffers = pruneOlderBuffers; } @Override @@ -1447,6 +1454,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { ArrayList<Long> removedTs = new ArrayList<>(); for (long ts : timestamps) { if (ts < timestamp) { + if (!mPruneOlderBuffers) { + Log.w(TAG, "Unexpected older image with ts: " + ts); + continue; + } Log.e(TAG, "Dropped image with ts: " + ts); Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts); if (entry.second != null) { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 5e5337337864..95526a8affbb 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -1403,6 +1403,7 @@ public final class OutputConfiguration implements Parcelable { if (mSurfaces.get(i) != other.mSurfaces.get(i)) return false; } + if (!mIsDeferredConfig && mSurfaces.size() != other.mSurfaces.size()) return false; if (mDynamicRangeProfile != other.mDynamicRangeProfile) { return false; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index aeddd0c8d4b1..8e49c4c165c4 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -46,6 +46,7 @@ import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -71,7 +72,11 @@ import java.util.function.Predicate; @SystemService(Context.DISPLAY_SERVICE) public final class DisplayManager { private static final String TAG = "DisplayManager"; - private static final boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayManager DEBUG && adb reboot' + static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) + || Log.isLoggable("DisplayManager_All", Log.DEBUG); private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = true; /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 2b5f5ee35a26..d2a15d1a2ea1 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -42,7 +42,6 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; @@ -82,7 +81,9 @@ public final class DisplayManagerGlobal { private static String sCurrentPackageName = ActivityThread.currentPackageName(); private static boolean sExtraDisplayListenerLogging = initExtraLogging(); - private static final boolean DEBUG = false || sExtraDisplayListenerLogging; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayManager DEBUG && adb reboot' + private static final boolean DEBUG = DisplayManager.DEBUG || sExtraDisplayListenerLogging; // True if display info and display ids should be cached. // @@ -412,6 +413,18 @@ public final class DisplayManagerGlobal { } } + /** + * Called when there is a display-related window configuration change. Reroutes the event from + * WindowManager to make sure the {@link Display} fields are up-to-date in the last callback. + * @param displayId the logical display that was changed. + */ + public void handleDisplayChangeFromWindowManager(int displayId) { + // There can be racing condition between DMS and WMS callbacks, so force triggering the + // listener to make sure the client can get the onDisplayChanged callback even if + // DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration). + handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */); + } + private static Looper getLooperForHandler(@Nullable Handler handler) { Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); if (looper == null) { @@ -470,7 +483,7 @@ public final class DisplayManagerGlobal { } } - private void handleDisplayEvent(int displayId, @DisplayEvent int event) { + private void handleDisplayEvent(int displayId, @DisplayEvent int event, boolean forceUpdate) { final DisplayInfo info; synchronized (mLock) { if (USE_CACHE) { @@ -501,7 +514,7 @@ public final class DisplayManagerGlobal { // Accepting an Executor means the listener may be synchronously invoked, so we must // not be holding mLock when we do so for (DisplayListenerDelegate listener : mDisplayListeners) { - listener.sendDisplayEvent(displayId, event, info); + listener.sendDisplayEvent(displayId, event, info, forceUpdate); } } @@ -1176,7 +1189,7 @@ public final class DisplayManagerGlobal { Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString( event)); } - handleDisplayEvent(displayId, event); + handleDisplayEvent(displayId, event, false /* forceUpdate */); } } @@ -1197,87 +1210,86 @@ public final class DisplayManagerGlobal { mPackageName = packageName; } - public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) { + void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info, + boolean forceUpdate) { 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(() -> { - // If the generation id's don't match we were canceled but still need to recycle() + // If the generation id's don't match we were canceled if (generationId == mGenerationId.get()) { - handleMessage(msg); + handleDisplayEventInner(displayId, event, info, forceUpdate); } - msg.recycle(); }); } - public void clearEvents() { + void clearEvents() { mGenerationId.incrementAndGet(); } - public void setEventsMask(@EventsMask long newEventsMask) { + void setEventsMask(@EventsMask long newEventsMask) { mEventsMask = newEventsMask; } - private void handleMessage(Message msg) { + private void handleDisplayEventInner(int displayId, @DisplayEvent int event, + @Nullable DisplayInfo info, boolean forceUpdate) { if (extraLogging()) { - Slog.i(TAG, "DLD(" + eventToString(msg.what) - + ", display=" + msg.arg1 + Slog.i(TAG, "DLD(" + eventToString(event) + + ", display=" + displayId + ", mEventsMask=" + Long.toBinaryString(mEventsMask) + ", mPackageName=" + mPackageName - + ", msg.obj=" + msg.obj + + ", displayInfo=" + info + ", listener=" + mListener.getClass() + ")"); } if (DEBUG) { Trace.beginSection( TextUtils.trimToSize( - "DLD(" + eventToString(msg.what) - + ", display=" + msg.arg1 + "DLD(" + eventToString(event) + + ", display=" + displayId + ", listener=" + mListener.getClass() + ")", 127)); } - switch (msg.what) { + switch (event) { case EVENT_DISPLAY_ADDED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { - mListener.onDisplayAdded(msg.arg1); + mListener.onDisplayAdded(displayId); } break; case EVENT_DISPLAY_CHANGED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { - DisplayInfo newInfo = (DisplayInfo) msg.obj; - if (newInfo != null && !newInfo.equals(mDisplayInfo)) { + if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) { if (extraLogging()) { Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " - + newInfo); + + info); } - mDisplayInfo.copyFrom(newInfo); - mListener.onDisplayChanged(msg.arg1); + mDisplayInfo.copyFrom(info); + mListener.onDisplayChanged(displayId); } } break; case EVENT_DISPLAY_BRIGHTNESS_CHANGED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) { - mListener.onDisplayChanged(msg.arg1); + mListener.onDisplayChanged(displayId); } break; case EVENT_DISPLAY_REMOVED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) { - mListener.onDisplayRemoved(msg.arg1); + mListener.onDisplayRemoved(displayId); } break; case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED: if ((mEventsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) { - mListener.onDisplayChanged(msg.arg1); + mListener.onDisplayChanged(displayId); } break; case EVENT_DISPLAY_CONNECTED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { - mListener.onDisplayConnected(msg.arg1); + mListener.onDisplayConnected(displayId); } break; case EVENT_DISPLAY_DISCONNECTED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { - mListener.onDisplayDisconnected(msg.arg1); + mListener.onDisplayDisconnected(displayId); } break; } diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index 4f07acf6961a..c5167dbc7d4c 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -17,6 +17,7 @@ package android.hardware.radio; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -45,7 +46,7 @@ public final class ProgramList implements AutoCloseable { private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, + private final ArrayMap<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>> mPrograms = new ArrayMap<>(); @GuardedBy("mLock") @@ -203,11 +204,11 @@ public final class ProgramList implements AutoCloseable { listCallbacksCopied = new ArrayList<>(mListCallbacks); if (chunk.isPurge()) { - Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, - RadioManager.ProgramInfo>>> programsIterator = - mPrograms.entrySet().iterator(); + Iterator<Map.Entry<ProgramSelector.Identifier, + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>> + programsIterator = mPrograms.entrySet().iterator(); while (programsIterator.hasNext()) { - Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, + Map.Entry<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>> removed = programsIterator.next(); if (removed.getValue() != null) { removedList.add(removed.getKey()); @@ -270,8 +271,7 @@ public final class ProgramList implements AutoCloseable { if (!mPrograms.containsKey(primaryKey)) { return; } - Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms - .get(primaryKey); + Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms.get(primaryKey); RadioManager.ProgramInfo removed = entries.remove(Objects.requireNonNull(key)); if (removed == null) return; if (entries.size() == 0) { @@ -287,15 +287,10 @@ public final class ProgramList implements AutoCloseable { public @NonNull List<RadioManager.ProgramInfo> toList() { List<RadioManager.ProgramInfo> list = new ArrayList<>(); synchronized (mLock) { - Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, - RadioManager.ProgramInfo>>> listIterator = mPrograms.entrySet().iterator(); - while (listIterator.hasNext()) { - Iterator<Map.Entry<UniqueProgramIdentifier, - RadioManager.ProgramInfo>> prorgramsIterator = listIterator.next() - .getValue().entrySet().iterator(); - while (prorgramsIterator.hasNext()) { - list.add(prorgramsIterator.next().getValue()); - } + for (int index = 0; index < mPrograms.size(); index++) { + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = + mPrograms.valueAt(index); + list.addAll(entries.values()); } } return list; @@ -304,9 +299,16 @@ public final class ProgramList implements AutoCloseable { /** * Returns the program with a specified primary identifier. * + * <p>This method only returns the first program from the list return from + * {@link #getProgramInfos} + * * @param id primary identifier of a program to fetch * @return the program info, or null if there is no such program on the list + * + * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs + * with the given primary identifier */ + @Deprecated public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; synchronized (mLock) { @@ -320,6 +322,29 @@ public final class ProgramList implements AutoCloseable { } /** + * Returns the program list with a specified primary identifier. + * + * @param id primary identifier of a program to fetch + * @return the program info list with the primary identifier, or empty list if there is no such + * program identifier on the list + * @throws NullPointerException if primary identifier is {@code null} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public @NonNull List<RadioManager.ProgramInfo> getProgramInfos( + @NonNull ProgramSelector.Identifier id) { + Objects.requireNonNull(id, "Primary identifier can not be null"); + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; + synchronized (mLock) { + entries = mPrograms.get(id); + } + + if (entries == null) { + return new ArrayList<>(); + } + return new ArrayList<>(entries.values()); + } + + /** * Filter for the program list. */ public static final class Filter implements Parcelable { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 12442ba12044..c7ec052b309b 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -16,6 +16,7 @@ package android.hardware.radio; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -124,6 +125,86 @@ public final class ProgramSelector implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ProgramType {} + /** + * Bitmask for HD radio subchannel 1 + * + * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is + * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7 are + * indexes of additional supplemental program services (SPS). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_1 = 1 << 0; + + /** + * Bitmask for HD radio subchannel 2 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_2 = 1 << 1; + + /** + * Bitmask for HD radio subchannel 3 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_3 = 1 << 2; + + /** + * Bitmask for HD radio subchannel 4 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_4 = 1 << 3; + + /** + * Bitmask for HD radio subchannel 5 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_5 = 1 << 4; + + /** + * Bitmask for HD radio subchannel 6 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_6 = 1 << 5; + + /** + * Bitmask for HD radio subchannel 7 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_7 = 1 << 6; + + /** + * Bitmask for HD radio subchannel 8 + * + * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int SUB_CHANNEL_HD_8 = 1 << 7; + + /** @hide */ + @IntDef(prefix = { "SUB_CHANNEL_HD_" }, value = { + SUB_CHANNEL_HD_1, + SUB_CHANNEL_HD_2, + SUB_CHANNEL_HD_3, + SUB_CHANNEL_HD_4, + SUB_CHANNEL_HD_5, + SUB_CHANNEL_HD_6, + SUB_CHANNEL_HD_7, + SUB_CHANNEL_HD_8, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HdSubChannel {} + public static final int IDENTIFIER_TYPE_INVALID = 0; /** * Primary identifier for analog (without RDS) AM/FM stations: @@ -147,19 +228,15 @@ public final class ProgramSelector implements Parcelable { * * <p>Consists of (from the LSB): * <li> - * <ul>32bit: Station ID number. - * <ul>4bit: HD_SUBCHANNEL. - * <ul>18bit: AMFM_FREQUENCY. + * <ul>32bit: Station ID number.</ul> + * <ul>4bit: HD subchannel, see {@link #SUB_CHANNEL_HD_1}.</ul> + * <ul>18bit: AMFM_FREQUENCY.</ul> * </li> * * <p>While station ID number should be unique globally, it sometimes gets * abused by broadcasters (i.e. not being set at all). To ensure local * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is - * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}. - * - * <p>HD Radio subchannel is a value in range of 0-7. - * This index is 0-based (where 0 is MPS and 1..7 are SPS), - * as opposed to HD Radio standard (where it's 1-based). + * a best-effort - see {@link #IDENTIFIER_TYPE_HD_STATION_NAME}. * * <p>The remaining bits should be set to zeros when writing on the chip side * and ignored when read. @@ -202,9 +279,9 @@ public final class ProgramSelector implements Parcelable { * * <p>Consists of (from the LSB): * <li> - * <ul>16bit: SId. - * <ul>8bit: ECC code. - * <ul>4bit: SCIdS. + * <ul>16bit: SId.</ul> + * <ul>8bit: ECC code.</ul> + * <ul>4bit: SCIdS.</ul> * </li> * * <p>SCIdS (Service Component Identifier within the Service) value @@ -238,18 +315,26 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** * 32bit primary identifier for SiriusXM Satellite Radio. + * + * @deprecated SiriusXM Satellite Radio is not supported */ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; - /** 0-999 range */ + /** + * 0-999 range + * + * @deprecated SiriusXM Satellite Radio is not supported + */ public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; /** * 44bit compound primary identifier for Digital Audio Broadcasting and * Digital Multimedia Broadcasting. * * <p>Consists of (from the LSB): - * - 32bit: SId; - * - 8bit: ECC code; - * - 4bit: SCIdS. + * <li> + * <ul>32bit: SId;</ul> + * <ul>8bit: ECC code;</ul> + * <ul>4bit: SCIdS.</ul> + * </li> * * <p>SCIdS (Service Component Identifier within the Service) value * of 0 represents the main service, while 1 and above represents @@ -260,6 +345,36 @@ public final class ProgramSelector implements Parcelable { */ public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; /** + * 64bit additional identifier for HD Radio representing station location. + * + * <p>Consists of (from the LSB): + * <li> + * <ul>4 bit: Bits 0:3 of altitude</ul> + * <ul>13 bit: Fractional bits of longitude</ul> + * <ul>8 bit: Integer bits of longitude</ul> + * <ul>1 bit: 0 for east and 1 for west for longitude</ul> + * <ul>1 bit: 0, representing longitude</ul> + * <ul>5 bit: pad of zeros separating longitude and latitude</ul> + * <ul>4 bit: Bits 4:7 of altitude</ul> + * <ul>13 bit: Fractional bits of latitude</ul> + * <ul>8 bit: Integer bits of latitude</ul> + * <ul>1 bit: 0 for north and 1 for south for latitude</ul> + * <ul>1 bit: 1, representing latitude</ul> + * <ul>5 bit: pad of zeros</ul> + * </li> + * + * <p>This format is defined in NRSC-5-C document: SY_IDD_1020s. + * + * <p>Due to Station ID abuse, some + * {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} identifiers may be not + * globally unique. To provide a best-effort solution, the station’s + * broadcast antenna containing the latitude and longitude may be + * carried as additional identifier and may be used by the tuner hardware + * to double-check tuning. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; + /** * Primary identifier for vendor-specific radio technology. * The value format is determined by a vendor. * @@ -300,6 +415,7 @@ public final class ProgramSelector implements Parcelable { IDENTIFIER_TYPE_SXM_SERVICE_ID, IDENTIFIER_TYPE_SXM_CHANNEL, IDENTIFIER_TYPE_DAB_DMB_SID_EXT, + IDENTIFIER_TYPE_HD_STATION_LOCATION, }) @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) @Retention(RetentionPolicy.SOURCE) diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 8c6083ce49b6..237ec0129ed9 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -18,6 +18,7 @@ package android.hardware.radio; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -159,12 +160,17 @@ public class RadioManager { /** * Forces the analog playback for the supporting radio technology. * - * User may disable digital playback for FM HD Radio or hybrid FM/DAB with - * this option. This is purely user choice, ie. does not reflect digital- + * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with + * this option. This is purely user choice, i.e. does not reflect digital- * analog handover state managed from the HAL implementation side. * - * Some radio technologies may not support this, ie. DAB. + * <p>Some radio technologies may not support this, i.e. DAB. + * + * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM} + * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet} + * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}. */ + @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. @@ -199,6 +205,30 @@ public class RadioManager { /** Enables DAB-FM soft-linking (related content). */ public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; + /** + * Forces the FM analog playback for the supporting radio technology. + * + * <p>User may disable FM digital playback for FM HD Radio or hybrid FM/DAB + * with this option. This is purely user choice, i.e. does not reflect + * digital-analog handover state managed from the HAL implementation side. + * + * <p>Some radio technologies may not support this, i.e. DAB. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int CONFIG_FORCE_ANALOG_FM = 10; + + /** + * Forces the AM analog playback for the supporting radio technology. + * + * <p>User may disable FM digital playback for AM HD Radio or hybrid AM/DAB + * with this option. This is purely user choice, i.e. does not reflect + * digital-analog handover state managed from the HAL implementation side. + * + * <p>Some radio technologies may not support this, i.e. DAB. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int CONFIG_FORCE_ANALOG_AM = 11; + /** @hide */ @IntDef(prefix = { "CONFIG_" }, value = { CONFIG_FORCE_MONO, @@ -210,6 +240,8 @@ public class RadioManager { CONFIG_DAB_FM_LINKING, CONFIG_DAB_DAB_SOFT_LINKING, CONFIG_DAB_FM_SOFT_LINKING, + CONFIG_FORCE_ANALOG_FM, + CONFIG_FORCE_ANALOG_AM, }) @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} @@ -1441,6 +1473,9 @@ public class RadioManager { private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; private static final int FLAG_TUNED = 1 << 4; private static final int FLAG_STEREO = 1 << 5; + private static final int FLAG_SIGNAL_ACQUIRED = 1 << 6; + private static final int FLAG_HD_SIS_ACQUIRED = 1 << 7; + private static final int FLAG_HD_AUDIO_ACQUIRED = 1 << 8; @NonNull private final ProgramSelector mSelector; @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo; @@ -1595,7 +1630,7 @@ public class RadioManager { } /** - * {@code true} if radio stream is not playing, ie. due to bad reception + * {@code true} if radio stream is not playing, i.e. due to bad reception * conditions or buffering. In this state volume knob MAY be disabled to * prevent user increasing volume too much. * It does NOT mean the user has muted audio. @@ -1621,6 +1656,28 @@ public class RadioManager { } /** + * @return {@code true} if the signal has been acquired. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isSignalAcquired() { + return (mInfoFlags & FLAG_SIGNAL_ACQUIRED) != 0; + } + /** + * @return {@code true} if HD Station Information Service (SIS) information is available. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isHdSisAvailable() { + return (mInfoFlags & FLAG_HD_SIS_ACQUIRED) != 0; + } + /** + * @return {@code true} if HD audio is available. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isHdAudioAvailable() { + return (mInfoFlags & FLAG_HD_AUDIO_ACQUIRED) != 0; + } + + /** * Signal quality (as opposed to the name) indication from 0 (no signal) * to 100 (excellent) * @return the signal quality indication. diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java index b7bf783754f7..db14c08b3698 100644 --- a/core/java/android/hardware/radio/RadioMetadata.java +++ b/core/java/android/hardware/radio/RadioMetadata.java @@ -15,6 +15,7 @@ */ package android.hardware.radio; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -30,6 +31,7 @@ import android.util.SparseArray; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -142,12 +144,84 @@ public final class RadioMetadata implements Parcelable { public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; + /** + * Short context description of comment + * + * <p>Comment could relate to the current audio program content, or it might + * be unrelated information that the station chooses to send. It is composed + * of short content description and actual text (see NRSC-G200-A and id3v2.3.0 + * for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = + "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; + + /** + * Actual text of comment + * + * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = + "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; + + /** + * Commercial + * + * <p>Commercial is application specific and generally used to facilitate the + * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMERCIAL = + "android.hardware.radio.metadata.COMMERCIAL"; + + /** + * Array of Unique File Identifiers + * + * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric + * identifier of the current content, or of an advertised product or + * service (see NRSC-G200-A and id3v2.3.0 for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; + + /** + * HD short station name or HD universal short station name + * + * <p>It can be up to 12 characters (see SY_IDD_1020s for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_STATION_NAME_SHORT = + "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; + + /** + * HD long station name, HD station slogan or HD station message + * + * <p>(see SY_IDD_1020s for more info) + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_STATION_NAME_LONG = + "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; + + /** + * Bit mask of all HD Radio subchannels available + * + * <p>Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the + * availability of HD-1 subchannel (main program service, MPS). Bits + * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8} + * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS) + * respectively. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = + "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; private static final int METADATA_TYPE_INVALID = -1; private static final int METADATA_TYPE_INT = 0; private static final int METADATA_TYPE_TEXT = 1; private static final int METADATA_TYPE_BITMAP = 2; private static final int METADATA_TYPE_CLOCK = 3; + private static final int METADATA_TYPE_TEXT_ARRAY = 4; private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; @@ -172,6 +246,13 @@ public final class RadioMetadata implements Parcelable { METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT); } // keep in sync with: system/media/radio/include/system/radio_metadata.h @@ -288,9 +369,12 @@ public final class RadioMetadata implements Parcelable { return false; } for (String key : mBundle.keySet()) { - // This logic will return a false negative if we ever put Bundles into mBundle. As of - // 2019-04-09, we only put ints, Strings, and Parcelables in, so it's fine for now. - if (!mBundle.get(key).equals(otherBundle.get(key))) { + if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), + METADATA_TYPE_TEXT_ARRAY)) { + if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) { + return false; + } + } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) { return false; } } @@ -326,7 +410,21 @@ public final class RadioMetadata implements Parcelable { sb.append(keyDisp); sb.append('='); - sb.append(mBundle.get(key)); + if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), + METADATA_TYPE_TEXT_ARRAY)) { + String[] stringArrayValue = mBundle.getStringArray(key); + sb.append('['); + for (int i = 0; i < stringArrayValue.length; i++) { + if (i != 0) { + sb.append(','); + } + sb.append(stringArrayValue[i]); + } + sb.append(']'); + } else { + sb.append(mBundle.get(key)); + } + } sb.append("]"); @@ -427,6 +525,36 @@ public final class RadioMetadata implements Parcelable { return clock; } + /** + * Gets the string array value associated with the given key as a string + * array. + * + * <p>Only string array keys may be used with this method: + * <ul> + * <li>{@link #METADATA_KEY_UFIDS}</li> + * </ul> + * + * @param key The key the value is stored under + * @return String array of the given string-array-type key + * @throws NullPointerException if metadata key is {@code null} + * @throws IllegalArgumentException if the metadata with the key is not found in + * metadata or the key is not of string-array type + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + @NonNull + public String[] getStringArray(@NonNull String key) { + Objects.requireNonNull(key, "Metadata key can not be null"); + if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { + throw new IllegalArgumentException("Failed to retrieve key " + key + + " as string array"); + } + String[] stringArrayValue = mBundle.getStringArray(key); + if (stringArrayValue == null) { + throw new IllegalArgumentException("Key " + key + " is not found in metadata"); + } + return stringArrayValue; + } + @Override public int describeContents() { return 0; @@ -539,6 +667,11 @@ public final class RadioMetadata implements Parcelable { * <li>{@link #METADATA_KEY_ARTIST}</li> * <li>{@link #METADATA_KEY_ALBUM}</li> * <li>{@link #METADATA_KEY_GENRE}</li> + * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li> + * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li> + * <li>{@link #METADATA_KEY_COMMERCIAL}</li> + * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li> + * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li> * </ul> * * @param key The key for referencing this value @@ -563,6 +696,7 @@ public final class RadioMetadata implements Parcelable { * <li>{@link #METADATA_KEY_RDS_PI}</li> * <li>{@link #METADATA_KEY_RDS_PTY}</li> * <li>{@link #METADATA_KEY_RBDS_PTY}</li> + * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li> * </ul> * or any bitmap represented by its identifier. * @@ -621,6 +755,35 @@ public final class RadioMetadata implements Parcelable { } /** + * Put a String array into the meta data. Custom keys may be used, but if + * the METADATA_KEYs defined in this class are used they may only be one + * of the following: + * <ul> + * <li>{@link #METADATA_KEY_UFIDS}</li> + * </ul> + * + * @param key The key for referencing this value + * @param value The String value to store + * @return the same Builder instance + * @throws NullPointerException if key or value is null + * @throws IllegalArgumentException if the key is not string-array-type key + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + @NonNull + public Builder putStringArray(@NonNull String key, @NonNull String[] value) { + Objects.requireNonNull(key, "Key can not be null"); + Objects.requireNonNull(value, "Value can not be null"); + if (!METADATA_KEYS_TYPE.containsKey(key) + || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { + throw new IllegalArgumentException("The " + key + + " key cannot be used to put a RadioMetadata String Array."); + } + mBundle.putStringArray(key, value); + return this; + } + + + /** * Creates a {@link RadioMetadata} instance with the specified fields. * * @return a new {@link RadioMetadata} object diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index bdbca91a715a..ba31ca3627bf 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -363,7 +363,7 @@ final class TunerAdapter extends RadioTuner { @Override public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) { try { - return mTuner.isConfigFlagSet(flag); + return mTuner.isConfigFlagSet(convertForceAnalogConfigFlag(flag)); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } @@ -372,7 +372,7 @@ final class TunerAdapter extends RadioTuner { @Override public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) { try { - mTuner.setConfigFlag(flag, value); + mTuner.setConfigFlag(convertForceAnalogConfigFlag(flag), value); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } @@ -411,4 +411,13 @@ final class TunerAdapter extends RadioTuner { return false; } } + + private @RadioManager.ConfigFlag int convertForceAnalogConfigFlag( + @RadioManager.ConfigFlag int flag) throws RemoteException { + if (Flags.hdRadioImproved() && flag == RadioManager.CONFIG_FORCE_ANALOG + && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) { + flag = RadioManager.CONFIG_FORCE_ANALOG_FM; + } + return flag; + } } diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index 55b0b4261763..cd50ace036de 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Flag for NFC reader option API changes" bug: "291187960" } + +flag { + name: "enable_nfc_user_restriction" + namespace: "nfc" + description: "Flag for NFC user restriction" + bug: "291187960" +} diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index cf3546057549..a1d2dcc74246 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -17,7 +17,7 @@ package android.os; import static android.Manifest.permission.PACKAGE_USAGE_STATS; -import static android.Manifest.permission.READ_LOGS; +import static android.Manifest.permission.READ_DROPBOX_DATA; import android.annotation.BytesLong; import android.annotation.CurrentTimeMillisLong; @@ -81,9 +81,12 @@ public class DropBoxManager { /** * Broadcast Action: This is broadcast when a new entry is added in the dropbox. - * You must hold the {@link android.Manifest.permission#READ_LOGS} permission - * in order to receive this broadcast. This broadcast can be rate limited for low priority - * entries + * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and later, you + * must hold the {@link android.Manifest.permission#READ_DROPBOX_DATA} permission + * in order to receive this broadcast. For apps targeting Android versions lower + * than {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, you must hold + * {@link android.Manifest.permission#READ_LOGS}. + * This broadcast can be rate limited for low priority entries * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -382,12 +385,17 @@ public class DropBoxManager { /** * Gets the next entry from the drop box <em>after</em> the specified time. * You must always call {@link Entry#close()} on the return value! + * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is + * required for apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + * and later. {@link android.Manifest.permission#READ_LOGS} permission is + * required for apps targeting Android versions lower than + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. * * @param tag of entry to look for, null for all tags * @param msec time of the last entry seen * @return the next entry, or null if there are no more entries */ - @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS }) + @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS }) public @Nullable Entry getNextEntry(String tag, long msec) { try { return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(), diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index cbc921391155..11084b88fad1 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -231,6 +232,7 @@ public final class PerformanceHintManager { * * @param enabled The flag that sets whether this session uses power-efficient scheduling. */ + @FlaggedApi(Flags.FLAG_ADPF_PREFER_POWER_EFFICIENCY) public void setPreferPowerEfficiency(boolean enabled) { nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled); } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 81d4e3abb9a4..47b6d8d6db30 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -2367,14 +2367,14 @@ public final class StrictMode { } /** Assume locked until we hear otherwise */ - private static volatile boolean sUserKeyUnlocked = false; + private static volatile boolean sCeStorageUnlocked = false; - private static boolean isUserKeyUnlocked(int userId) { + private static boolean isCeStorageUnlocked(int userId) { final IStorageManager storage = IStorageManager.Stub .asInterface(ServiceManager.getService("mount")); if (storage != null) { try { - return storage.isUserKeyUnlocked(userId); + return storage.isCeStorageUnlocked(userId); } catch (RemoteException ignored) { } } @@ -2387,13 +2387,13 @@ public final class StrictMode { // since any relocking of that user will always result in our // process being killed to release any CE FDs we're holding onto. if (userId == UserHandle.myUserId()) { - if (sUserKeyUnlocked) { + if (sCeStorageUnlocked) { return; - } else if (isUserKeyUnlocked(userId)) { - sUserKeyUnlocked = true; + } else if (isCeStorageUnlocked(userId)) { + sCeStorageUnlocked = true; return; } - } else if (isUserKeyUnlocked(userId)) { + } else if (isCeStorageUnlocked(userId)) { return; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 9034ff10286b..72bc2113f93f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -23,6 +23,7 @@ import android.Manifest; import android.accounts.AccountManager; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -58,6 +59,7 @@ import android.graphics.BitmapFactory; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.location.LocationManager; +import android.nfc.Flags; import android.provider.Settings; import android.util.AndroidException; import android.util.ArraySet; @@ -1871,6 +1873,7 @@ public class UserManager { * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION) public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio"; diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index c4521c036329..10b9e3a15834 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -34,3 +34,10 @@ flag { description: "Introduce a constant as maximum value of bugreport mode." bug: "305067125" } + +flag { + name: "adpf_prefer_power_efficiency" + namespace: "game" + description: "Guards the ADPF power efficiency API" + bug: "288117936" +} diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 369a1932e437..3ecf74e75367 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -134,20 +134,20 @@ interface IStorageManager { @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS") void setDebugFlags(int flags, int mask) = 60; @EnforcePermission("STORAGE_INTERNAL") - void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61; + void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61; @EnforcePermission("STORAGE_INTERNAL") - void destroyUserKey(int userId) = 62; + void destroyUserStorageKeys(int userId) = 62; @EnforcePermission("STORAGE_INTERNAL") - void unlockUserKey(int userId, int serialNumber, in byte[] secret) = 63; + void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63; @EnforcePermission("STORAGE_INTERNAL") - void lockUserKey(int userId) = 64; - boolean isUserKeyUnlocked(int userId) = 65; + void lockCeStorage(int userId) = 64; + boolean isCeStorageUnlocked(int userId) = 65; @EnforcePermission("STORAGE_INTERNAL") void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66; @EnforcePermission("STORAGE_INTERNAL") void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67; @EnforcePermission("STORAGE_INTERNAL") - void setUserKeyProtection(int userId, in byte[] secret) = 70; + void setCeStorageProtection(int userId, in byte[] secret) = 70; @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS") void fstrim(int flags, IVoldTaskListener listener) = 72; AppFuseMount mountProxyFileDescriptorBridge() = 73; diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index ee387e7c284f..2d1802ae85e5 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1589,28 +1589,64 @@ public class StorageManager { DEFAULT_FULL_THRESHOLD_BYTES); } - /** {@hide} */ - public void createUserKey(int userId, int serialNumber, boolean ephemeral) { + /** + * Creates the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage. + * <p> + * This creates the user's CE key and DE key for internal storage, then adds them to the kernel. + * Then, if the user is not ephemeral, this stores the DE key (encrypted) on flash. (The CE key + * is not stored until {@link IStorageManager#setCeStorageProtection()}.) + * <p> + * This does not create the CE and DE directories themselves. For that, see {@link + * #prepareUserStorage()}. + * <p> + * This is only intended to be called by UserManagerService, as part of creating a user. + * + * @param userId ID of the user + * @param serialNumber serial number of the user + * @param ephemeral whether the user is ephemeral + * @throws RuntimeException on error. The user's keys already existing is considered an error. + * @hide + */ + public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) { try { - mStorageManager.createUserKey(userId, serialNumber, ephemeral); + mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - /** {@hide} */ - public void destroyUserKey(int userId) { + /** + * Destroys the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage. + * <p> + * This evicts the keys from the kernel (if present), which "locks" the corresponding + * directories. Then, this deletes the encrypted keys from flash. This operates on all the + * user's CE and DE keys, for both internal and adoptable storage. + * <p> + * This does not destroy the CE and DE directories themselves. For that, see {@link + * #destroyUserStorage()}. + * <p> + * This is only intended to be called by UserManagerService, as part of removing a user. + * + * @param userId ID of the user + * @throws RuntimeException on error. On error, as many things as possible are still destroyed. + * @hide + */ + public void destroyUserStorageKeys(int userId) { try { - mStorageManager.destroyUserKey(userId); + mStorageManager.destroyUserStorageKeys(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - /** {@hide} */ - public void lockUserKey(int userId) { + /** + * Locks the user's credential-encrypted (CE) storage. + * + * @hide + */ + public void lockCeStorage(int userId) { try { - mStorageManager.lockUserKey(userId); + mStorageManager.lockCeStorage(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1637,17 +1673,26 @@ public class StorageManager { /** {@hide} */ @TestApi public static boolean isUserKeyUnlocked(int userId) { + return isCeStorageUnlocked(userId); + } + + /** + * Returns true if the user's credential-encrypted (CE) storage is unlocked. + * + * @hide + */ + public static boolean isCeStorageUnlocked(int userId) { if (sStorageManager == null) { sStorageManager = IStorageManager.Stub .asInterface(ServiceManager.getService("mount")); } if (sStorageManager == null) { - Slog.w(TAG, "Early during boot, assuming locked"); + Slog.w(TAG, "Early during boot, assuming CE storage is locked"); return false; } final long token = Binder.clearCallingIdentity(); try { - return sStorageManager.isUserKeyUnlocked(userId); + return sStorageManager.isCeStorageUnlocked(userId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 3f06a91f6e5b..0798f6523a23 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -35,3 +35,10 @@ flag { description: "enable the shouldRegisterAttributionSource API" bug: "305057691" } + +flag { + name: "attribution_source_constructor" + namespace: "permissions" + description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)" + bug: "304478648" +}
\ No newline at end of file diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f0906b1e3c06..a2f1ce1e0efa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11653,6 +11653,15 @@ public final class Settings { "accessibility_magnification_joystick_enabled"; /** + * Setting that specifies whether the display magnification is enabled via a system-wide + * two fingers triple tap gesture. + * + * @hide + */ + public static final String ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED = + "accessibility_magnification_two_finger_triple_tap_enabled"; + + /** * Controls magnification enable gesture. Accessibility magnification can have one or more * enable gestures. * @@ -12058,6 +12067,13 @@ public final class Settings { public static final String DND_CONFIGS_MIGRATED = "dnd_settings_migrated"; /** + * Controls whether to hide private space entry point in All Apps + * + * @hide + */ + public static final String HIDE_PRIVATESPACE_ENTRY_POINT = "hide_privatespace_entry_point"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig index 293143595b5e..52d4d47007a3 100644 --- a/core/java/android/service/notification/flags.aconfig +++ b/core/java/android/service/notification/flags.aconfig @@ -7,3 +7,11 @@ flag { bug: "284297289" } +flag { + name: "notification_lifetime_extension_refactor" + namespace: "systemui" + description: "Enables moving notification lifetime extension management from SystemUI to " + "Notification Manager Service" + bug: "299448097" +} + diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java index db97d4f52643..dfb1361efec1 100644 --- a/core/java/android/service/voice/AbstractDetector.java +++ b/core/java/android/service/voice/AbstractDetector.java @@ -263,5 +263,12 @@ abstract class AbstractDetector implements HotwordDetector { result != null ? result : new HotwordRejectedResult.Builder().build()); })); } + + @Override + public void onTrainingData(HotwordTrainingData data) { + Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { + mCallback.onTrainingData(data); + })); + } } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 6a82f6da67b3..875031fb0cb3 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -306,6 +306,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { private static final int MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE = 9; private static final int MSG_DETECTION_SOUND_TRIGGER_FAILURE = 10; private static final int MSG_DETECTION_UNKNOWN_FAILURE = 11; + private static final int MSG_HOTWORD_TRAINING_DATA = 12; private final String mText; private final Locale mLocale; @@ -1653,6 +1654,16 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @Override + public void onTrainingData(@NonNull HotwordTrainingData data) { + if (DBG) { + Slog.d(TAG, "onTrainingData(" + data + ")"); + } else { + Slog.i(TAG, "onTrainingData"); + } + Message.obtain(mHandler, MSG_HOTWORD_TRAINING_DATA, data).sendToTarget(); + } + + @Override public void onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure); @@ -1783,6 +1794,9 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { case MSG_DETECTION_UNKNOWN_FAILURE: mExternalCallback.onUnknownFailure((String) message.obj); break; + case MSG_HOTWORD_TRAINING_DATA: + mExternalCallback.onTrainingData((HotwordTrainingData) message.obj); + break; default: super.handleMessage(message); } diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index ccf8b67826c8..13b6a9a79535 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -19,6 +19,7 @@ package android.service.voice; import static java.util.Objects.requireNonNull; import android.annotation.DurationMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +40,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; +import android.service.voice.flags.Flags; import android.speech.IRecognitionServiceManager; import android.util.Log; import android.view.contentcapture.ContentCaptureManager; @@ -443,5 +445,30 @@ public abstract class HotwordDetectionService extends Service throw e.rethrowFromSystemServer(); } } + + /** + * Informs the {@link HotwordDetector} when there is training data. + * + * <p> A daily limit of 20 is enforced on training data events sent. Number events egressed + * are tracked across UTC day (24-hour window) and count is reset at midnight + * (UTC 00:00:00). To be informed of failures to egress training data due to limit being + * reached, the associated hotword detector should listen for + * {@link HotwordDetectionServiceFailure#ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED} + * events in {@link HotwordDetector.Callback#onFailure(HotwordDetectionServiceFailure)}. + * + * @param data Training data determined by the service. This is provided to the + * {@link HotwordDetector}. + */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + public void onTrainingData(@NonNull HotwordTrainingData data) { + requireNonNull(data); + try { + Log.d(TAG, "onTrainingData"); + mRemoteCallback.onTrainingData(data); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/service/voice/HotwordDetectionServiceFailure.java b/core/java/android/service/voice/HotwordDetectionServiceFailure.java index 5cf245d8624b..420dac185cb4 100644 --- a/core/java/android/service/voice/HotwordDetectionServiceFailure.java +++ b/core/java/android/service/voice/HotwordDetectionServiceFailure.java @@ -16,12 +16,14 @@ package android.service.voice; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import android.service.voice.flags.Flags; import android.text.TextUtils; import java.lang.annotation.Retention; @@ -79,6 +81,14 @@ public final class HotwordDetectionServiceFailure implements Parcelable { */ public static final int ERROR_CODE_REMOTE_EXCEPTION = 7; + /** Indicates failure to egress training data due to limit being exceeded. */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8; + + /** Indicates failure to egress training data due to security exception. */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9; + /** * @hide */ diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java index 32a93eef7fda..16a6dbe2956b 100644 --- a/core/java/android/service/voice/HotwordDetector.java +++ b/core/java/android/service/voice/HotwordDetector.java @@ -19,6 +19,7 @@ package android.service.voice; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -27,6 +28,7 @@ import android.media.AudioFormat; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.SharedMemory; +import android.service.voice.flags.Flags; import java.io.PrintWriter; @@ -244,6 +246,19 @@ public interface HotwordDetector { void onRejected(@NonNull HotwordRejectedResult result); /** + * Called by the {@link HotwordDetectionService} to egress training data to the + * {@link HotwordDetector}. This data can be used for improving and analyzing hotword + * detection models. + * + * @param data Training data to be egressed provided by the + * {@link HotwordDetectionService}. + */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + default void onTrainingData(@NonNull HotwordTrainingData data) { + return; + } + + /** * Called when the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is * created by the system and given a short amount of time to report their initialization * state. diff --git a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java new file mode 100644 index 000000000000..76e506cc6728 --- /dev/null +++ b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.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.service.voice; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Enforces daily limits on the egress of {@link HotwordTrainingData} from the hotword detection + * service. + * + * <p> Egress is tracked across UTC day (24-hour window) and count is reset at + * midnight (UTC 00:00:00). + * + * @hide + */ +public class HotwordTrainingDataLimitEnforcer { + private static final String TAG = "HotwordTrainingDataLimitEnforcer"; + + /** + * Number of hotword training data events that are allowed to be egressed per day. + */ + private static final int TRAINING_DATA_EGRESS_LIMIT = 20; + + /** + * Name of hotword training data limit shared preference. + */ + private static final String TRAINING_DATA_LIMIT_SHARED_PREF = "TrainingDataSharedPref"; + + /** + * Key for date associated with + * {@link HotwordTrainingDataLimitEnforcer#TRAINING_DATA_EGRESS_COUNT}. + */ + private static final String TRAINING_DATA_EGRESS_DATE = "TRAINING_DATA_EGRESS_DATE"; + + /** + * Key for number of hotword training data events egressed on + * {@link HotwordTrainingDataLimitEnforcer#TRAINING_DATA_EGRESS_DATE}. + */ + private static final String TRAINING_DATA_EGRESS_COUNT = "TRAINING_DATA_EGRESS_COUNT"; + + private SharedPreferences mSharedPreferences; + + private static final Object INSTANCE_LOCK = new Object(); + private final Object mTrainingDataIncrementLock = new Object(); + + private static HotwordTrainingDataLimitEnforcer sInstance; + + /** Get singleton HotwordTrainingDataLimitEnforcer instance. */ + public static @NonNull HotwordTrainingDataLimitEnforcer getInstance(@NonNull Context context) { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new HotwordTrainingDataLimitEnforcer(context.getApplicationContext()); + } + return sInstance; + } + } + + private HotwordTrainingDataLimitEnforcer(Context context) { + mSharedPreferences = context.getSharedPreferences( + new File(Environment.getDataSystemCeDirectory(UserHandle.USER_SYSTEM), + TRAINING_DATA_LIMIT_SHARED_PREF), + Context.MODE_PRIVATE); + } + + /** @hide */ + @VisibleForTesting + public void resetTrainingDataEgressCount() { + Log.i(TAG, "Resetting training data egress count!"); + synchronized (mTrainingDataIncrementLock) { + // Clear all training data shared preferences. + mSharedPreferences.edit().clear().commit(); + } + } + + /** + * Increments training data egress count. + * <p> If count exceeds daily training data egress limit, returns false. Else, will return true. + */ + public boolean incrementEgressCount() { + synchronized (mTrainingDataIncrementLock) { + return incrementTrainingDataEgressCountLocked(); + } + } + + private boolean incrementTrainingDataEgressCountLocked() { + SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + dt.setTimeZone(TimeZone.getTimeZone("UTC")); + String currentDate = dt.format(new Date()); + + String storedDate = mSharedPreferences.getString(TRAINING_DATA_EGRESS_DATE, ""); + int storedCount = mSharedPreferences.getInt(TRAINING_DATA_EGRESS_COUNT, 0); + Log.i(TAG, + TextUtils.formatSimple("There are %s hotword training data events egressed for %s", + storedCount, storedDate)); + + SharedPreferences.Editor editor = mSharedPreferences.edit(); + + // If date has not changed from last training data event, increment counter if within + // limit. + if (storedDate.equals(currentDate)) { + if (storedCount < TRAINING_DATA_EGRESS_LIMIT) { + Log.i(TAG, "Within hotword training data egress limit, incrementing..."); + editor.putInt(TRAINING_DATA_EGRESS_COUNT, storedCount + 1); + editor.commit(); + return true; + } + Log.i(TAG, "Exceeded hotword training data egress limit."); + return false; + } + + // If date has changed, reset. + Log.i(TAG, TextUtils.formatSimple( + "Stored date %s is different from current data %s. Resetting counters...", + storedDate, currentDate)); + + editor.putString(TRAINING_DATA_EGRESS_DATE, currentDate); + editor.putInt(TRAINING_DATA_EGRESS_COUNT, 1); + editor.commit(); + return true; + } +} diff --git a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl index c6b10ff05b08..a9c6af79a0e1 100644 --- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl +++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl @@ -18,6 +18,7 @@ package android.service.voice; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; /** * Callback for returning the detected result from the HotwordDetectionService. @@ -37,4 +38,10 @@ oneway interface IDspHotwordDetectionCallback { * Sends {@code result} to the HotwordDetector. */ void onRejected(in HotwordRejectedResult result); + + /** + * Called by {@link HotwordDetectionService} to egress training data to the + * {@link HotwordDetector}. + */ + void onTrainingData(in HotwordTrainingData data); } diff --git a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl index fab830af9d48..62267729f144 100644 --- a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl +++ b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl @@ -20,6 +20,7 @@ import android.media.AudioFormat; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; /** * Callback for returning the detected result from the HotwordDetectionService. @@ -47,4 +48,10 @@ oneway interface IMicrophoneHotwordDetectionVoiceInteractionCallback { */ void onRejected( in HotwordRejectedResult hotwordRejectedResult); + + /** + * Called by {@link HotwordDetectionService} to egress training data to the + * {@link HotwordDetector}. + */ + void onTrainingData(in HotwordTrainingData data); } diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index f1bc792696d6..2c68faea5141 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -18,6 +18,7 @@ package android.service.voice; import static android.Manifest.permission.RECORD_AUDIO; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.hardware.soundtrigger.SoundTrigger; @@ -201,6 +202,13 @@ class SoftwareHotwordDetector extends AbstractDetector { result != null ? result : new HotwordRejectedResult.Builder().build()); })); } + + @Override + public void onTrainingData(@NonNull HotwordTrainingData result) { + Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { + mCallback.onTrainingData(result); + })); + } } private static class InitializationStateListener @@ -238,6 +246,13 @@ class SoftwareHotwordDetector extends AbstractDetector { } @Override + public void onTrainingData(@NonNull HotwordTrainingData data) { + if (DEBUG) { + Slog.i(TAG, "Ignored #onTrainingData event"); + } + } + + @Override public void onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure) throws RemoteException { diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index b5448d4374b2..91de894c1d93 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -369,6 +369,12 @@ public class VisualQueryDetector { Slog.i(TAG, "Ignored #onRejected event"); } } + @Override + public void onTrainingData(HotwordTrainingData data) throws RemoteException { + if (DEBUG) { + Slog.i(TAG, "Ignored #onTrainingData event"); + } + } @Override public void onRecognitionPaused() throws RemoteException { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index d2806217a276..42203d4b0d71 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -18,6 +18,7 @@ package android.service.voice; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -48,6 +49,7 @@ import android.os.ServiceManager; import android.os.SharedMemory; import android.os.SystemProperties; import android.provider.Settings; +import android.service.voice.flags.Flags; import android.util.ArraySet; import android.util.Log; @@ -443,6 +445,20 @@ public class VoiceInteractionService extends Service { } } + /** Reset hotword training data egressed count. + * @hide */ + @TestApi + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + @RequiresPermission(Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT) + public final void resetHotwordTrainingDataEgressCountForTest() { + Log.i(TAG, "Resetting hotword training data egress count for test."); + try { + mSystemService.resetHotwordTrainingDataEgressCountForTest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. * This instance must be retained and used by the client. diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 517ae4f5c81b..5f6a9bd068c9 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -457,12 +457,21 @@ public class PrecomputedText implements Spannable { } else { hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE; } + LineBreakConfig config = params.getLineBreakConfig(); + if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO + && pct.getParagraphCount() != 1) { + // If the text has multiple paragraph, resolve line break word style auto to none. + config = new LineBreakConfig.Builder() + .merge(config) + .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) + .build(); + } ArrayList<ParagraphInfo> result = new ArrayList<>(); for (int i = 0; i < pct.getParagraphCount(); ++i) { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd, + params.getTextPaint(), config, pct, paraStart, paraEnd, params.getTextDirection(), hyphenationMode, computeLayout, true, pct.getMeasuredParagraph(i), null /* no recycle */))); } @@ -489,6 +498,7 @@ public class PrecomputedText implements Spannable { hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE; } + LineBreakConfig config = null; int paraEnd = 0; for (int paraStart = start; paraStart < end; paraStart = paraEnd) { paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); @@ -500,8 +510,21 @@ public class PrecomputedText implements Spannable { paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. } + if (config == null) { + config = params.getLineBreakConfig(); + if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO + && !(paraStart == start && paraEnd == end)) { + // If the text has multiple paragraph, resolve line break word style auto to + // none. + config = new LineBreakConfig.Builder() + .merge(config) + .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) + .build(); + } + } + result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd, + params.getTextPaint(), config, text, paraStart, paraEnd, params.getTextDirection(), hyphenationMode, computeLayout, computeBounds, null /* no hint */, null /* no recycle */))); diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 201f680860f5..43c38f319713 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -61,3 +61,17 @@ flag { description: "Feature flag for preventing horizontal clipping." bug: "63938206" } + +flag { + name: "deprecate_ui_fonts" + namespace: "text" + description: "Feature flag for deprecating UI fonts. By setting true for this feature flag, the elegant text height of will be turned on by default unless explicitly setting it to false by attribute or Java API call." + bug: "279646685" +} + +flag { + name: "word_style_auto" + namespace: "text" + description: "A feature flag that implements line break word style auto." + bug: "280005585" +} diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 1ec7c41e4395..17a3a12d3b79 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -28,6 +28,7 @@ import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION; import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS; import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH; import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX; +import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; @@ -469,8 +470,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame); - final boolean visible = mPendingFraction == 0 && source != null - ? source.isVisible() + // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is animated from + // the hidden state. + final boolean visible = mPendingFraction == 0 + ? mAnimationType != ANIMATION_TYPE_SHOW : !mFinished || mShownOnFinish; if (outState != null && source != null) { diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 99a7fe598936..aad3bf2679d9 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -20,11 +20,9 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; -import android.annotation.TestApi; import android.annotation.UiContext; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -42,15 +40,11 @@ import android.widget.FrameLayout; import com.android.internal.R; -import dalvik.system.PathClassLoader; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.util.HashMap; import java.util.Objects; @@ -82,12 +76,6 @@ public abstract class LayoutInflater { private static final String TAG = LayoutInflater.class.getSimpleName(); private static final boolean DEBUG = false; - private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex"; - /** - * Whether or not we use the precompiled layout. - */ - private static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled"; - /** Empty stack trace used to avoid log spam in re-throw exceptions. */ private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; @@ -116,13 +104,6 @@ public abstract class LayoutInflater { private Factory2 mPrivateFactory; private Filter mFilter; - // Indicates whether we should try to inflate layouts using a precompiled layout instead of - // inflating from the XML resource. - private boolean mUseCompiledView; - // This variable holds the classloader that will be used to look for precompiled layouts. The - // The classloader includes the generated compiled_view.dex file. - private ClassLoader mPrecompiledClassLoader; - /** * This is not a public API. Two APIs are now available to alleviate the need to access * this directly: {@link #createView(Context, String, String, AttributeSet)} and @@ -259,7 +240,6 @@ public abstract class LayoutInflater { protected LayoutInflater(Context context) { StrictMode.assertConfigurationContext(context, "LayoutInflater"); mContext = context; - initPrecompiledViews(); } /** @@ -277,7 +257,6 @@ public abstract class LayoutInflater { mFactory2 = original.mFactory2; mPrivateFactory = original.mPrivateFactory; setFilter(original.mFilter); - initPrecompiledViews(); } /** @@ -419,57 +398,6 @@ public abstract class LayoutInflater { } } - private void initPrecompiledViews() { - // Precompiled layouts are not supported in this release. - boolean enabled = false; - initPrecompiledViews(enabled); - } - - private void initPrecompiledViews(boolean enablePrecompiledViews) { - mUseCompiledView = enablePrecompiledViews; - - if (!mUseCompiledView) { - mPrecompiledClassLoader = null; - return; - } - - // Make sure the application allows code generation - ApplicationInfo appInfo = mContext.getApplicationInfo(); - if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) { - mUseCompiledView = false; - return; - } - - // Try to load the precompiled layout file. - try { - mPrecompiledClassLoader = mContext.getClassLoader(); - String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME; - if (new File(dexFile).exists()) { - mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader); - } else { - // If the precompiled layout file doesn't exist, then disable precompiled - // layouts. - mUseCompiledView = false; - } - } catch (Throwable e) { - if (DEBUG) { - Log.e(TAG, "Failed to initialized precompiled views layouts", e); - } - mUseCompiledView = false; - } - if (!mUseCompiledView) { - mPrecompiledClassLoader = null; - } - } - - /** - * @hide for use by CTS tests - */ - @TestApi - public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) { - initPrecompiledViews(enablePrecompiledLayouts); - } - /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. @@ -529,10 +457,6 @@ public abstract class LayoutInflater { + Integer.toHexString(resource) + ")"); } - View view = tryInflatePrecompiled(resource, res, root, attachToRoot); - if (view != null) { - return view; - } XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); @@ -541,54 +465,6 @@ public abstract class LayoutInflater { } } - private @Nullable - View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, - boolean attachToRoot) { - if (!mUseCompiledView) { - return null; - } - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)"); - - // Try to inflate using a precompiled layout. - String pkg = res.getResourcePackageName(resource); - String layout = res.getResourceEntryName(resource); - - try { - Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader); - Method inflater = clazz.getMethod(layout, Context.class, int.class); - View view = (View) inflater.invoke(null, mContext, resource); - - if (view != null && root != null) { - // We were able to use the precompiled inflater, but now we need to do some work to - // attach the view to the root correctly. - XmlResourceParser parser = res.getLayout(resource); - try { - AttributeSet attrs = Xml.asAttributeSet(parser); - advanceToRootNode(parser); - ViewGroup.LayoutParams params = root.generateLayoutParams(attrs); - - if (attachToRoot) { - root.addView(view, params); - } else { - view.setLayoutParams(params); - } - } finally { - parser.close(); - } - } - - return view; - } catch (Throwable e) { - if (DEBUG) { - Log.e(TAG, "Failed to use precompiled view", e); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - return null; - } - /** * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is * found. @@ -1050,7 +926,7 @@ public abstract class LayoutInflater { * of the general view creation logic, and thus may return {@code null} for some tags. This * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects. * - * @hide for use by precompiled layouts. + * @hide originally for internal use by precompiled layouts, which have since been removed. * * @param parent the parent view, used to inflate layout params * @param name the name of the XML tag used to define the view @@ -1217,85 +1093,80 @@ public abstract class LayoutInflater { + "reference. The layout ID " + value + " is not valid."); } - final View precompiled = tryInflatePrecompiled(layout, context.getResources(), - (ViewGroup) parent, /*attachToRoot=*/true); - if (precompiled == null) { - final XmlResourceParser childParser = context.getResources().getLayout(layout); - - try { - final AttributeSet childAttrs = Xml.asAttributeSet(childParser); - - while ((type = childParser.next()) != XmlPullParser.START_TAG && - type != XmlPullParser.END_DOCUMENT) { - // Empty. - } + final XmlResourceParser childParser = context.getResources().getLayout(layout); + try { + final AttributeSet childAttrs = Xml.asAttributeSet(childParser); - if (type != XmlPullParser.START_TAG) { - throw new InflateException(getParserStateDescription(context, childAttrs) - + ": No start tag found!"); - } + while ((type = childParser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Empty. + } - final String childName = childParser.getName(); + if (type != XmlPullParser.START_TAG) { + throw new InflateException(getParserStateDescription(context, childAttrs) + + ": No start tag found!"); + } - if (TAG_MERGE.equals(childName)) { - // The <merge> tag doesn't support android:theme, so - // nothing special to do here. - rInflate(childParser, parent, context, childAttrs, false); - } else { - final View view = createViewFromTag(parent, childName, - context, childAttrs, hasThemeOverride); - final ViewGroup group = (ViewGroup) parent; - - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.Include); - final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); - final int visibility = a.getInt(R.styleable.Include_visibility, -1); - a.recycle(); - - // We try to load the layout params set in the <include /> tag. - // If the parent can't generate layout params (ex. missing width - // or height for the framework ViewGroups, though this is not - // necessarily true of all ViewGroups) then we expect it to throw - // a runtime exception. - // We catch this exception and set localParams accordingly: true - // means we successfully loaded layout params from the <include> - // tag, false means we need to rely on the included layout params. - ViewGroup.LayoutParams params = null; - try { - params = group.generateLayoutParams(attrs); - } catch (RuntimeException e) { - // Ignore, just fail over to child attrs. - } - if (params == null) { - params = group.generateLayoutParams(childAttrs); - } - view.setLayoutParams(params); + final String childName = childParser.getName(); - // Inflate all children. - rInflateChildren(childParser, view, childAttrs, true); + if (TAG_MERGE.equals(childName)) { + // The <merge> tag doesn't support android:theme, so + // nothing special to do here. + rInflate(childParser, parent, context, childAttrs, false); + } else { + final View view = + createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride); + final ViewGroup group = (ViewGroup) parent; + + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Include); + final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); + final int visibility = a.getInt(R.styleable.Include_visibility, -1); + a.recycle(); + + // We try to load the layout params set in the <include /> tag. + // If the parent can't generate layout params (ex. missing width + // or height for the framework ViewGroups, though this is not + // necessarily true of all ViewGroups) then we expect it to throw + // a runtime exception. + // We catch this exception and set localParams accordingly: true + // means we successfully loaded layout params from the <include> + // tag, false means we need to rely on the included layout params. + ViewGroup.LayoutParams params = null; + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + // Ignore, just fail over to child attrs. + } + if (params == null) { + params = group.generateLayoutParams(childAttrs); + } + view.setLayoutParams(params); - if (id != View.NO_ID) { - view.setId(id); - } + // Inflate all children. + rInflateChildren(childParser, view, childAttrs, true); - switch (visibility) { - case 0: - view.setVisibility(View.VISIBLE); - break; - case 1: - view.setVisibility(View.INVISIBLE); - break; - case 2: - view.setVisibility(View.GONE); - break; - } + if (id != View.NO_ID) { + view.setId(id); + } - group.addView(view); + switch (visibility) { + case 0: + view.setVisibility(View.VISIBLE); + break; + case 1: + view.setVisibility(View.INVISIBLE); + break; + case 2: + view.setVisibility(View.GONE); + break; } - } finally { - childParser.close(); + + group.addView(view); } + } finally { + childParser.close(); } + LayoutInflater.consumeChildElements(parser); } diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index c2b519619690..d9b5b2d725e2 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -804,7 +804,7 @@ public final class WindowContainerTransaction implements Parcelable { * Sets/removes the always on top flag for this {@code windowContainer}. See * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. * Please note that this method is only intended to be used for a - * {@link com.android.server.wm.DisplayArea}. + * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}. * * <p> * Setting always on top to {@code True} will also make the {@code windowContainer} to move diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index 0cbfcc501918..c81c9eccfa3d 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -129,7 +129,6 @@ public abstract class WindowProviderService extends Service implements WindowPro @SuppressLint({"OnNameExpected", "ExecutorRegistration"}) // Suppress lint because this is a legacy named function and doesn't have an optional param // for executor. - // TODO(b/259347943): Update documentation for U. /** * Here we override to prevent WindowProviderService from invoking * {@link Application.registerComponentCallback}, which will result in callback registered diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl index ba87caa0697c..a65877c7a951 100644 --- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl +++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl @@ -20,6 +20,7 @@ import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; import android.service.voice.SoundTriggerFailure; import android.service.voice.VisualQueryDetectionServiceFailure; import com.android.internal.infra.AndroidFuture; @@ -59,6 +60,12 @@ oneway interface IHotwordRecognitionStatusCallback { void onRejected(in HotwordRejectedResult result); /** + * Called by {@link HotwordDetectionService} to egress training data to the + * {@link HotwordDetector}. + */ + void onTrainingData(in HotwordTrainingData data); + + /** * Called when the detection fails due to an error occurs in the * {@link HotwordDetectionService}. * diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 314ed69cb885..68e2b48c8f08 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -359,6 +359,12 @@ interface IVoiceInteractionManagerService { in IHotwordRecognitionStatusCallback callback); /** + * Test API to reset training data egress count for test. + */ + @EnforcePermission("RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT") + void resetHotwordTrainingDataEgressCountForTest(); + + /** * Starts to listen the status of visible activity. */ void startListeningVisibleActivityChanged(in IBinder token); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index a3e27062fa7b..8d11672144b2 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1934,15 +1934,21 @@ public class LockPatternUtils { } /** - * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e. - * doesn't have an LSKF. + * If the user is not secured, ie doesn't have an LSKF, then decrypt the user's synthetic + * password and use it to unlock various cryptographic keys associated with the user. This + * primarily includes unlocking the user's credential-encrypted (CE) storage. It also includes + * deriving or decrypting the vendor auth secret and sending it to the AuthSecret HAL. + * <p> + * These tasks would normally be done when the LSKF is verified. This method is where these + * tasks are done when the user doesn't have an LSKF. It's called when the user is started. + * <p> + * Except on permission denied, this method doesn't throw an exception on failure. However, the + * last thing that it does is unlock CE storage, and whether CE storage has been successfully + * unlocked can be determined by {@link StorageManager#isCeStorageUnlocked()}. * <p> - * Whether the storage has been unlocked can be determined by - * {@link StorageManager#isUserKeyUnlocked()}. - * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. * - * @param userId the ID of the user whose storage to unlock + * @param userId the ID of the user whose keys to unlock */ public void unlockUserKeyIfUnsecured(@UserIdInt int userId) { try { diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto new file mode 100644 index 000000000000..8c3304137904 --- /dev/null +++ b/core/proto/android/app/appstartinfo.proto @@ -0,0 +1,42 @@ +/* + * 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. + */ + +syntax = "proto2"; +option java_multiple_files = true; + +package android.app; + +import "frameworks/base/core/proto/android/privacy.proto"; +import "frameworks/proto_logging/stats/enums/app/enums.proto"; + +/** + * An android.app.ApplicationStartInfo object. + */ +message ApplicationStartInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 pid = 1; + optional int32 real_uid = 2; + optional int32 package_uid = 3; + optional int32 defining_uid = 4; + optional string process_name = 5; + optional AppStartStartupState startup_state = 6; + optional AppStartReasonCode reason = 7; + optional bytes startup_timestamps = 8; + optional AppStartStartType start_type = 9; + optional bytes start_intent = 10; + optional AppStartLaunchMode launch_mode = 11; +} diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 473270229bdb..5b0a5027dde2 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -99,6 +99,7 @@ message SecureSettingsProto { optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_magnification_gesture = 53 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_magnification_two_finger_triple_tap_enabled = 54 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 025a57d0e334..c5889ba78159 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -20,6 +20,7 @@ package com.android.server.am; import "frameworks/base/core/proto/android/app/activitymanager.proto"; import "frameworks/base/core/proto/android/app/appexitinfo.proto"; +import "frameworks/base/core/proto/android/app/appstartinfo.proto"; import "frameworks/base/core/proto/android/app/notification.proto"; import "frameworks/base/core/proto/android/app/profilerinfo.proto"; import "frameworks/base/core/proto/android/content/component_name.proto"; @@ -1041,3 +1042,23 @@ message AppsExitInfoProto { } repeated Package packages = 2; } + +// sync with com.android.server.am.am.ProcessList.java +message AppsStartInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 last_update_timestamp = 1; + message Package { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string package_name = 1; + message User { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + repeated .android.app.ApplicationStartInfoProto app_start_info = 2; + } + repeated User users = 2; + } + repeated Package packages = 2; +} diff --git a/core/proto/android/service/OWNERS b/core/proto/android/service/OWNERS index 70cb50f75362..7a19155e4278 100644 --- a/core/proto/android/service/OWNERS +++ b/core/proto/android/service/OWNERS @@ -1 +1,2 @@ per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com +per-file package.proto = file:platform/frameworks/base:/PACKAGE_MANAGER_OWNERS diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6daa5b934284..8c91be8b21c0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4565,6 +4565,12 @@ <permission android:name="android.permission.SET_DEBUG_APP" android:protectionLevel="signature|privileged|development" /> + <!-- Allows an application to access the data in Dropbox. + <p>Not for use by third-party applications. + @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") --> + <permission android:name="android.permission.READ_DROPBOX_DATA" + android:protectionLevel="signature|privileged|development" /> + <!-- Allows an application to set the maximum number of (not needed) application processes that can be running. <p>Not for use by third-party applications. --> @@ -4647,7 +4653,7 @@ @hide @SystemApi --> <permission android:name="android.permission.STATUS_BAR_SERVICE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|recents" /> <!-- Allows an application to bind to third party quick settings tiles. <p>Should only be requested by the System, should be required by @@ -4711,7 +4717,7 @@ @hide --> <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" - android:protectionLevel="signature|module" /> + android:protectionLevel="signature|module|recents" /> <!-- Allows an application to avoid all toast rate limiting restrictions. <p>Not for use by third-party applications. @@ -7768,6 +7774,14 @@ <permission android:name="android.permission.MANAGE_DISPLAYS" android:protectionLevel="signature" /> + <!-- @SystemApi Allows apps to reset hotword training data egress count for testing. + <p>CTS tests will use UiAutomation.AdoptShellPermissionIdentity() to gain access. + <p>Protection level: signature + @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") + @hide --> + <permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml index 66dd392d685f..b664fe4f9c4e 100644 --- a/core/res/res/drawable-watch/ic_lock_bugreport.xml +++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml @@ -20,12 +20,12 @@ android:viewportHeight="24.0" android:tint="@android:color/white"> <path - android:fillColor="#FF000000" + android:fillColor="@android:color/white" android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/> <path - android:fillColor="#FF000000" + android:fillColor="@android:color/white" android:pathData="M10,14h4v2h-4z"/> <path - android:fillColor="#FF000000" + android:fillColor="@android:color/white" android:pathData="M10,10h4v2h-4z"/> </vector> diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml index 34bc88cad19a..b437a4b70cca 100644 --- a/core/res/res/drawable-watch/ic_lock_power_off.xml +++ b/core/res/res/drawable-watch/ic_lock_power_off.xml @@ -21,6 +21,6 @@ android:viewportHeight="24" android:tint="@android:color/white"> <path - android:fillColor="@android:color/black" + android:fillColor="@android:color/white" android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/> </vector> diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml index 24d7c347f673..52933aae8fe0 100644 --- a/core/res/res/drawable-watch/ic_restart.xml +++ b/core/res/res/drawable-watch/ic_restart.xml @@ -21,6 +21,6 @@ android:viewportHeight="24" android:tint="@android:color/white"> <path - android:fillColor="@android:color/black" + android:fillColor="@android:color/white" android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/> </vector> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5a1f2d1ae680..ab71b41bccd4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1241,6 +1241,21 @@ <!-- Activity name for the default target activity to be launched. [DO NOT TRANSLATE] --> <string name="config_primaryShortPressTargetActivity" translatable="false"></string> + <!-- Whether a single short press on POWER should be launched without multi-press delay. + When this value is set to true, POWER button's single short press behavior will execute + immediately upon key-up regardless of whether this press will be part of a multi-press + gesture in the future(therefore, not waiting for a multi-press detecting delay). + + For Example, let's say a power button single press launches the app menu and power button + double press launches the camera. By configuring this variable to true, user will observe 2 + things in order upon a double press: + 1. App menu pops up immediately upon the first key up. + 2. Camera starts as the double press behavior. + + Note that this config has no effect on long press behavior. + --> + <bool name="config_shortPressEarlyOnPower">false</bool> + <!-- Control the behavior of the search key. 0 - Launch default search activity 1 - Launch target activity defined by config_searchKeyTargetActivity diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8e1c09ede581..14bbb966f750 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -471,6 +471,7 @@ <java-symbol type="string" name="config_primaryShortPressTargetActivity" /> <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" /> <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" /> + <java-symbol type="bool" name="config_shortPressEarlyOnPower" /> <java-symbol type="string" name="config_doublePressOnPowerTargetActivity" /> <java-symbol type="integer" name="config_searchKeyBehavior" /> <java-symbol type="string" name="config_searchKeyTargetActivity" /> diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 054d10c336e6..6be553b99c3c 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -40,6 +40,8 @@ android_test { "androidx.test.rules", "truth", "testng", + "android.hardware.radio.flags-aconfig-java", + "flag-junit", "mockito-target-extended", ], libs: ["android.test.base"], diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java index d638fedc5358..d4a88c49b38f 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java @@ -16,8 +16,6 @@ package android.hardware.radio; -import static com.google.common.truth.Truth.assertWithMessage; - import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -36,8 +34,14 @@ import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.Parcel; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; +import com.google.common.truth.Expect; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -143,12 +147,17 @@ public final class ProgramListTest { @Mock private RadioTuner.Callback mTunerCallbackMock; + @Rule + public final Expect mExpect = Expect.create(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void getIdentifierTypes_forFilter() { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes()) + mExpect.withMessage("Filtered identifier types").that(filter.getIdentifierTypes()) .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES); } @@ -157,7 +166,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filtered identifiers").that(filter.getIdentifiers()) + mExpect.withMessage("Filtered identifiers").that(filter.getIdentifiers()) .containsExactlyElementsIn(FILTER_IDENTIFIERS); } @@ -166,7 +175,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter including categories") + mExpect.withMessage("Filter including categories") .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES); } @@ -175,7 +184,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter excluding modifications") + mExpect.withMessage("Filter excluding modifications") .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS); } @@ -184,7 +193,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter vendor obtained from filter without vendor filter") + mExpect.withMessage("Filter vendor obtained from filter without vendor filter") .that(filter.getVendorFilter()).isNull(); } @@ -192,13 +201,13 @@ public final class ProgramListTest { public void getVendorFilter_forFilterWithVendorFilter() { ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER); - assertWithMessage("Filter vendor obtained from filter with vendor filter") + mExpect.withMessage("Filter vendor obtained from filter with vendor filter") .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER); } @Test public void describeContents_forFilter() { - assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0); + mExpect.withMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0); } @Test @@ -206,7 +215,7 @@ public final class ProgramListTest { ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Hash code of the same filter") + mExpect.withMessage("Hash code of the same filter") .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode()); } @@ -214,7 +223,7 @@ public final class ProgramListTest { public void hashCode_withDifferentFilters_notEquals() { ProgramList.Filter filterCompared = new ProgramList.Filter(); - assertWithMessage("Hash code of the different filter") + mExpect.withMessage("Hash code of the different filter") .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode()); } @@ -227,7 +236,7 @@ public final class ProgramListTest { ProgramList.Filter filterFromParcel = ProgramList.Filter.CREATOR.createFromParcel(parcel); - assertWithMessage("Filter created from parcel") + mExpect.withMessage("Filter created from parcel") .that(filterFromParcel).isEqualTo(TEST_FILTER); } @@ -235,36 +244,37 @@ public final class ProgramListTest { public void newArray_forFilterCreator() { ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE); - assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE); + mExpect.withMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE); } @Test public void isPurge_forChunk() { - assertWithMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE); + mExpect.withMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE); } @Test public void isComplete_forChunk() { - assertWithMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete()) + mExpect.withMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete()) .isEqualTo(IS_COMPLETE); } @Test public void getModified_forChunk() { - assertWithMessage("Modified program info in chunk") + mExpect.withMessage("Modified program info in chunk") .that(FM_DAB_ADD_CHUNK.getModified()) .containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @Test public void getRemoved_forChunk() { - assertWithMessage("Removed program identifiers in chunk") + mExpect.withMessage("Removed program identifiers in chunk") .that(FM_DAB_ADD_CHUNK.getRemoved()).containsExactly(RDS_UNIQUE_IDENTIFIER); } @Test public void describeContents_forChunk() { - assertWithMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()).isEqualTo(0); + mExpect.withMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()) + .isEqualTo(0); } @Test @@ -276,7 +286,7 @@ public final class ProgramListTest { ProgramList.Chunk chunkFromParcel = ProgramList.Chunk.CREATOR.createFromParcel(parcel); - assertWithMessage("Chunk created from parcel") + mExpect.withMessage("Chunk created from parcel") .that(chunkFromParcel).isEqualTo(FM_DAB_ADD_CHUNK); } @@ -284,7 +294,7 @@ public final class ProgramListTest { public void newArray_forChunkCreator() { ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE); - assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE); + mExpect.withMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE); } @Test @@ -295,7 +305,7 @@ public final class ProgramListTest { IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> mRadioTuner.getProgramList(parameters)); - assertWithMessage("Exception for getting program list when not ready") + mExpect.withMessage("Exception for getting program list when not ready") .that(thrown).hasMessageThat().contains("Program list is not ready yet"); } @@ -308,7 +318,7 @@ public final class ProgramListTest { RuntimeException thrown = assertThrows(RuntimeException.class, () -> mRadioTuner.getProgramList(parameters)); - assertWithMessage("Exception for getting program list when service is dead") + mExpect.withMessage("Exception for getting program list when service is dead") .that(thrown).hasMessageThat().contains("Service died"); } @@ -330,7 +340,7 @@ public final class ProgramListTest { ProgramList nullProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); - assertWithMessage("Exception for radio HAL client not supporting program list") + mExpect.withMessage("Exception for radio HAL client not supporting program list") .that(nullProgramList).isNull(); } @@ -344,7 +354,7 @@ public final class ProgramListTest { mRadioTuner.getDynamicProgramList(TEST_FILTER); }); - assertWithMessage("Exception for radio HAL client service died") + mExpect.withMessage("Exception for radio HAL client service died") .that(thrown).hasMessageThat().contains("Service died"); } @@ -360,7 +370,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER); verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete(); - assertWithMessage("Program info in program list after adding FM and DAB info") + mExpect.withMessage("Program info in program list after adding FM and DAB info") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @@ -378,7 +388,7 @@ public final class ProgramListTest { mTunerCallback.onProgramListUpdated(fmRemovedChunk); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER); - assertWithMessage("Program info in program list after removing FM id") + mExpect.withMessage("Program info in program list after removing FM id") .that(mProgramList.toList()).containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @@ -397,7 +407,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved( DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program info in program list after removing part of DAB ids") + mExpect.withMessage("Program info in program list after removing part of DAB ids") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_2); } @@ -419,7 +429,7 @@ public final class ProgramListTest { mTunerCallback.onProgramListUpdated(dabRemovedChunk2); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program info in program list after removing all DAB ids") + mExpect.withMessage("Program info in program list after removing all DAB ids") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO); } @@ -448,7 +458,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program list after purge chunk applied") + mExpect.withMessage("Program list after purge chunk applied") .that(mProgramList.toList()).isEmpty(); } @@ -607,6 +617,49 @@ public final class ProgramListTest { verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates(); } + @Test + public void get() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + + mExpect.withMessage( + "FM program info in program list after updating with chunk of FM program") + .that(mProgramList.get(FM_IDENTIFIER)).isEqualTo(FM_PROGRAM_INFO); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getProgramInfos() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER); + + mExpect.withMessage("FM program info in program list") + .that(mProgramList.getProgramInfos(FM_IDENTIFIER)).containsExactly(FM_PROGRAM_INFO); + mExpect.withMessage("All DAB program info in program list") + .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)) + .containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getProgramInfos_withIdNotFound() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + + mExpect.withMessage("DAB program info in program list") + .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)).isEmpty(); + } + private static ProgramSelector createProgramSelector(int programType, ProgramSelector.Identifier identifier) { return new ProgramSelector(programType, identifier, /* secondaryIds= */ null, diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index b9f4c3fa0a77..03de1430fec8 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -32,8 +32,12 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArrayMap; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -42,6 +46,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -90,10 +95,26 @@ public final class RadioManagerTest { private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties(/* dabFrequencyTable= */ null); + private static final int DAB_INFO_FLAG_LIVE_VALUE = 1; + private static final int DAB_INFO_FLAG_TUNED_VALUE = 1 << 4; + private static final int DAB_INFO_FLAG_STEREO_VALUE = 1 << 5; + private static final int HD_INFO_FLAG_LIVE_VALUE = 1; + private static final int HD_INFO_FLAG_TUNED_VALUE = 1 << 4; + private static final int HD_INFO_FLAG_STEREO_VALUE = 1 << 5; + private static final int HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE = 1 << 6; + private static final int HD_INFO_FLAG_SIS_ACQUISITION_VALUE = 1 << 7; /** - * Info flags with live, tuned and stereo enabled + * Info flags with live, tuned, and stereo enabled for DAB program */ - private static final int INFO_FLAGS = 0b110001; + private static final int INFO_FLAGS_DAB = DAB_INFO_FLAG_LIVE_VALUE | DAB_INFO_FLAG_TUNED_VALUE + | DAB_INFO_FLAG_STEREO_VALUE; + /** + * HD program info flags with live, tuned, stereo enabled, signal acquired, SIS information + * available but audio unavailable + */ + private static final int INFO_FLAGS_HD = HD_INFO_FLAG_LIVE_VALUE | HD_INFO_FLAG_TUNED_VALUE + | HD_INFO_FLAG_STEREO_VALUE | HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE + | HD_INFO_FLAG_SIS_ACQUISITION_VALUE; private static final int SIGNAL_QUALITY = 2; private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER = new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, @@ -112,9 +133,20 @@ public final class RadioManagerTest { new ProgramSelector.Identifier[]{ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null); + + private static final long HD_FREQUENCY = 97_100; + private static final ProgramSelector.Identifier HD_STATION_EXT_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, + /* value= */ (HD_FREQUENCY << 36) | 0x1L); + private static final ProgramSelector HD_SELECTOR = new ProgramSelector( + ProgramSelector.PROGRAM_TYPE_FM_HD, HD_STATION_EXT_IDENTIFIER, + new ProgramSelector.Identifier[]{}, /* vendorIds= */ null); + private static final RadioMetadata METADATA = createMetadata(); private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO = createDabProgramInfo(DAB_SELECTOR); + private static final RadioManager.ProgramInfo HD_PROGRAM_INFO = createHdProgramInfo( + HD_SELECTOR); private static final int EVENT_ANNOUNCEMENT_TYPE = Announcement.TYPE_EVENT; private static final List<Announcement> TEST_ANNOUNCEMENT_LIST = Arrays.asList( @@ -135,6 +167,9 @@ public final class RadioManagerTest { @Mock private ICloseHandle mCloseHandleMock; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void getType_forBandDescriptor() { RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); @@ -927,6 +962,27 @@ public final class RadioManagerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isSignalAcquired_forProgramInfo() { + assertWithMessage("Signal acquisition status for HD program info") + .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isHdSisAvailable_forProgramInfo() { + assertWithMessage("SIS information acquisition status for HD program") + .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isHdAudioAvailable_forProgramInfo() { + assertWithMessage("Audio acquisition status for HD program") + .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse(); + } + + @Test public void getSignalStrength_forProgramInfo() { assertWithMessage("Signal strength of DAB program info") .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY); @@ -1156,9 +1212,18 @@ public final class RadioManagerTest { } private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) { - return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER, - DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS, - SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null); + return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), + DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), + INFO_FLAGS_DAB, SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null); + } + + private static RadioManager.ProgramInfo createHdProgramInfo(ProgramSelector selector) { + long frequency = (selector.getPrimaryId().getValue() >> 32); + ProgramSelector.Identifier physicallyTunedToId = new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, frequency); + return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), physicallyTunedToId, + Collections.emptyList(), INFO_FLAGS_HD, SIGNAL_QUALITY, METADATA, + /* vendorInfo= */ null); } private void createRadioManager() throws RemoteException { diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java index e348a51f6214..3891accbba44 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java @@ -22,12 +22,18 @@ import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.os.Parcel; +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 org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Arrays; import java.util.Set; @RunWith(MockitoJUnitRunner.class) @@ -35,6 +41,8 @@ public final class RadioMetadataTest { private static final int CREATOR_ARRAY_SIZE = 3; private static final int INT_KEY_VALUE = 1; + private static final String ARTIST_KEY_VALUE = "artistTest"; + private static final String[] UFIDS_VALUE = new String[]{"ufid1", "ufid2"}; private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200; private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1; @@ -43,6 +51,9 @@ public final class RadioMetadataTest { @Mock private Bitmap mBitmapValue; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void describeContents_forClock() { RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH, @@ -97,7 +108,7 @@ public final class RadioMetadataTest { mBuilder.putInt(invalidIntKey, INT_KEY_VALUE); }); - assertWithMessage("Exception for putting illegal int-value key %s", invalidIntKey) + assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey) .that(thrown).hasMessageThat() .matches(".*" + invalidIntKey + ".*cannot.*int.*?"); } @@ -117,6 +128,42 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withIllegalKey_throwsException() { + String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE); + }); + + assertWithMessage("Exception for putting illegal string-array-value for key %s", + invalidStringArrayKey).that(thrown).hasMessageThat() + .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withNullKey_throwsException() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE); + }); + + assertWithMessage("Exception for putting string-array with null key") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withNullString_throwsException() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null); + }); + + assertWithMessage("Exception for putting null string-array") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test public void containsKey_withKeyInMetadata() { String key = RadioMetadata.METADATA_KEY_RDS_PI; RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build(); @@ -156,11 +203,10 @@ public final class RadioMetadataTest { @Test public void getString_withKeyInMetadata() { String key = RadioMetadata.METADATA_KEY_ARTIST; - String value = "artistTest"; - RadioMetadata metadata = mBuilder.putString(key, value).build(); + RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build(); assertWithMessage("String value for key %s in metadata", key) - .that(metadata.getString(key)).isEqualTo(value); + .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE); } @Test @@ -235,10 +281,62 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withKeyInMetadata() { + String key = RadioMetadata.METADATA_KEY_UFIDS; + RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build(); + + assertWithMessage("String-array value for key %s not in metadata", key) + .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withKeyNotInMetadata() { + String key = RadioMetadata.METADATA_KEY_UFIDS; + RadioMetadata metadata = mBuilder.build(); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + metadata.getStringArray(key); + }); + + assertWithMessage("Exception for getting string array for string-array value for key %s " + + "not in metadata", key).that(thrown).hasMessageThat().contains("not found"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withNullKey() { + RadioMetadata metadata = mBuilder.build(); + + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + metadata.getStringArray(/* key= */ null); + }); + + assertWithMessage("Exception for getting string array with null key") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withInvalidKey() { + String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; + RadioMetadata metadata = mBuilder.build(); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + metadata.getStringArray(invalidClockKey); + }); + + assertWithMessage("Exception for getting string array for key %s not of string-array type", + invalidClockKey).that(thrown).hasMessageThat() + .contains("string array"); + } + + @Test public void size_withNonEmptyMetadata() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .build(); assertWithMessage("Size of fields in non-empty metadata") @@ -257,7 +355,7 @@ public final class RadioMetadataTest { public void keySet_withNonEmptyMetadata() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue) .build(); @@ -291,7 +389,7 @@ public final class RadioMetadataTest { public void equals_forMetadataWithSameContents_returnsTrue() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .build(); RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata); RadioMetadata metadataCopied = copyBuilder.build(); @@ -315,10 +413,29 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata() { RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) + .build(); + Parcel parcel = Parcel.obtain(); + + metadataExpected.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel); + assertWithMessage("Radio metadata created from parcel") + .that(metadataFromParcel).isEqualTo(metadataExpected); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() { + RadioMetadata metadataExpected = mBuilder + .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) + .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE) .build(); Parcel parcel = Parcel.obtain(); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 6a6a951c94c8..7ca806b49b68 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,9 +36,14 @@ import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; +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 org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -77,6 +83,9 @@ public final class TunerAdapterTest { @Mock private RadioTuner.Callback mCallbackMock; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws Exception { mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION; @@ -604,6 +613,44 @@ public final class TunerAdapterTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog with feature flag enabled and force FM supported") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported() + throws Exception { + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(false); + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog with feature flag enabled but force FM unsupported") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse(); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog without Force FM enabled") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse(); + } + + @Test public void isConfigFlagSet_whenServiceDied_fails() throws Exception { when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException()); @@ -636,6 +683,43 @@ public final class TunerAdapterTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG), + anyBoolean()); + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG_FM, false); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(false); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false); + verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG_FM), + anyBoolean()); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false); + } + + @Test public void getParameters_forTunerAdapter() throws Exception { List<String> parameterKeys = List.of("ParameterKeyMock"); Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock"); diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 7f3e01432dd9..9430ba6a939a 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -857,7 +857,8 @@ public class NotificationTest { assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor()); assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor()); assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor()); - assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor()); + assertEquals(cDay.getOnTertiaryAccentTextColor(), + cNight.getOnTertiaryAccentTextColor()); assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor()); assertEquals(cDay.getContrastColor(), cNight.getContrastColor()); assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha()); @@ -1830,7 +1831,7 @@ public class NotificationTest { assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); - assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID); + assertThat(c.getOnTertiaryAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getRippleAlpha()).isAtLeast(0x00); @@ -1848,7 +1849,7 @@ public class NotificationTest { assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1); // The text that is used within the accent color DOES need to have contrast - assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5); + assertContrastIsAtLeast(c.getOnTertiaryAccentTextColor(), c.getTertiaryAccentColor(), 4.5); } private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor, diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 0f62b1c23ab4..930b1a4dde26 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -16,14 +16,19 @@ package android.app.servertransaction; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.clearInvocations; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.IDisplayManager; +import android.os.Handler; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -34,8 +39,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.function.IntConsumer; - /** * Tests for {@link ClientTransactionListenerController}. * @@ -47,30 +50,36 @@ import java.util.function.IntConsumer; @Presubmit public class ClientTransactionListenerControllerTest { @Mock - private IntConsumer mDisplayChangeListener; + private IDisplayManager mIDisplayManager; + @Mock + private DisplayManager.DisplayListener mListener; + private DisplayManagerGlobal mDisplayManager; + private Handler mHandler; private ClientTransactionListenerController mController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mController = spy(ClientTransactionListenerController.createInstanceForTesting()); + mDisplayManager = new DisplayManagerGlobal(mIDisplayManager); + mHandler = getInstrumentation().getContext().getMainThreadHandler(); + mController = spy(ClientTransactionListenerController.createInstanceForTesting( + mDisplayManager)); doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled(); } @Test - public void testRegisterDisplayChangeListener() { - mController.registerDisplayChangeListener(mDisplayChangeListener, Runnable::run); + public void testOnDisplayChanged() throws RemoteException { + // Mock IDisplayManager to return a display info to trigger display change. + final DisplayInfo newDisplayInfo = new DisplayInfo(); + doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123); - mController.onDisplayChanged(123); + mDisplayManager.registerDisplayListener(mListener, mHandler, + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */); - verify(mDisplayChangeListener).accept(123); - - clearInvocations(mDisplayChangeListener); - mController.unregisterDisplayChangeListener(mDisplayChangeListener); - - mController.onDisplayChanged(321); + mController.onDisplayChanged(123); + mHandler.runWithScissors(() -> { }, 0); - verify(mDisplayChangeListener, never()).accept(anyInt()); + verify(mListener).onDisplayChanged(123); } } diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java new file mode 100644 index 000000000000..2ec58d43477d --- /dev/null +++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java @@ -0,0 +1,164 @@ +/* + * 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.usage; + +import static android.view.Surface.ROTATION_90; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; + +import android.app.usage.UsageEvents.Event; +import android.content.res.Configuration; +import android.os.Parcel; +import android.test.suitebuilder.annotation.LargeTest; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class ParcelableUsageEventListTest { + private static final int SMALL_TEST_EVENT_COUNT = 100; + private static final int LARGE_TEST_EVENT_COUNT = 10000; + + private Random mRandom = new Random(); + + @Test + public void testSmallList() throws Exception { + testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT); + } + + @Test + public void testLargeList() throws Exception { + testParcelableUsageEventList(LARGE_TEST_EVENT_COUNT); + } + + private void testParcelableUsageEventList(int eventCount) throws Exception { + List<Event> smallList = new ArrayList<>(); + for (int i = 0; i < eventCount; i++) { + smallList.add(generateUsageEvent()); + } + + ParcelableUsageEventList slice; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0); + parcel.setDataPosition(0); + slice = parcel.readParcelable(getClass().getClassLoader(), + ParcelableUsageEventList.class); + } finally { + parcel.recycle(); + } + + assertNotNull(slice); + assertNotNull(slice.getList()); + assertEquals(eventCount, slice.getList().size()); + + for (int i = 0; i < eventCount; i++) { + compareUsageEvent(smallList.get(i), slice.getList().get(i)); + } + } + + private Event generateUsageEvent() { + final Event event = new Event(); + event.mEventType = mRandom.nextInt(Event.MAX_EVENT_TYPE + 1); + event.mPackage = anyString(); + event.mClass = anyString(); + event.mTimeStamp = anyLong(); + event.mInstanceId = anyInt(); + event.mTimeStamp = anyLong(); + + switch (event.mEventType) { + case Event.CONFIGURATION_CHANGE: + event.mConfiguration = new Configuration(); + event.mConfiguration.seq = anyInt(); + event.mConfiguration.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES; + event.mConfiguration.smallestScreenWidthDp = 100; + event.mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + event.mConfiguration.windowConfiguration.setRotation(ROTATION_90); + break; + case Event.SHORTCUT_INVOCATION: + event.mShortcutId = anyString(); + break; + case Event.CHOOSER_ACTION: + event.mAction = anyString(); + event.mContentType = anyString(); + event.mContentAnnotations = new String[mRandom.nextInt(10)]; + for (int i = 0; i < event.mContentAnnotations.length; i++) { + event.mContentAnnotations[i] = anyString(); + } + break; + case Event.STANDBY_BUCKET_CHANGED: + event.mBucketAndReason = anyInt(); + break; + case Event.NOTIFICATION_INTERRUPTION: + event.mNotificationChannelId = anyString(); + break; + case Event.LOCUS_ID_SET: + event.mLocusId = anyString(); + break; + } + + event.mFlags = anyInt(); + return event; + } + + private static void compareUsageEvent(Event ue1, Event ue2) { + assertEquals(ue1.mPackage, ue2.mPackage); + assertEquals(ue1.mClass, ue2.mClass); + assertEquals(ue1.mTaskRootPackage, ue2.mTaskRootPackage); + assertEquals(ue1.mTaskRootClass, ue2.mTaskRootClass); + assertEquals(ue1.mInstanceId, ue2.mInstanceId); + assertEquals(ue1.mEventType, ue2.mEventType); + assertEquals(ue1.mTimeStamp, ue2.mTimeStamp); + + switch (ue1.mEventType) { + case Event.CONFIGURATION_CHANGE: + assertEquals(ue1.mConfiguration, ue2.mConfiguration); + break; + case Event.SHORTCUT_INVOCATION: + assertEquals(ue1.mShortcutId, ue2.mShortcutId); + break; + case Event.CHOOSER_ACTION: + assertEquals(ue1.mAction, ue2.mAction); + assertEquals(ue1.mContentType, ue2.mContentType); + assertTrue(Arrays.equals(ue1.mContentAnnotations, ue2.mContentAnnotations)); + break; + case Event.STANDBY_BUCKET_CHANGED: + assertEquals(ue1.mBucketAndReason, ue2.mBucketAndReason); + break; + case Event.NOTIFICATION_INTERRUPTION: + assertEquals(ue1.mNotificationChannelId, ue1.mNotificationChannelId); + break; + case Event.LOCUS_ID_SET: + assertEquals(ue1.mLocusId, ue2.mLocusId); + break; + } + + assertEquals(ue1.mFlags, ue2.mFlags); + } +} diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java deleted file mode 100644 index d8c9baa8df5b..000000000000 --- a/core/tests/coretests/src/android/content/BrickDeniedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2008 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.content; - -import android.test.AndroidTestCase; - -import androidx.test.filters.SmallTest; - -/** Test to make sure brick intents <b>don't</b> work without permission. */ -public class BrickDeniedTest extends AndroidTestCase { - @SmallTest - public void testBrick() { - // Try both the old and new brick intent names. Neither should work, - // since this test application doesn't have the required permission. - // If it does work, well, the test certainly won't pass. - getContext().sendBroadcast(new Intent("SHES_A_BRICK_HOUSE")); - getContext().sendBroadcast(new Intent("android.intent.action.BRICK")); - } -} diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index c2e6b60cd680..969ae8e819da 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -16,12 +16,19 @@ package android.hardware.display; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Handler; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -37,6 +44,13 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +/** + * Tests for {@link DisplayManagerGlobal}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:DisplayManagerGlobalTest + */ +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class DisplayManagerGlobalTest { @@ -51,6 +65,9 @@ public class DisplayManagerGlobalTest { @Mock private DisplayManager.DisplayListener mListener; + @Mock + private DisplayManager.DisplayListener mListener2; + @Captor private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor; @@ -82,7 +99,11 @@ public class DisplayManagerGlobalTest { Mockito.verifyNoMoreInteractions(mListener); Mockito.reset(mListener); - callback.onDisplayEvent(1, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + // Mock IDisplayManager to return a different display info to trigger display change. + final DisplayInfo newDisplayInfo = new DisplayInfo(); + newDisplayInfo.rotation++; + doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); waitForHandler(); Mockito.verify(mListener).onDisplayChanged(eq(displayId)); Mockito.verifyNoMoreInteractions(mListener); @@ -161,7 +182,44 @@ public class DisplayManagerGlobalTest { mDisplayManagerGlobal.unregisterDisplayListener(mListener); inOrder.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L)); + } + + @Test + public void testHandleDisplayChangeFromWindowManager() throws RemoteException { + // Mock IDisplayManager to return a display info to trigger display change. + final DisplayInfo newDisplayInfo = new DisplayInfo(); + doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(123); + doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(321); + + // Nothing happens when there is no listener. + mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(123); + + // One listener listens on add/remove, and the other one listens on change. + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, + DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */); + mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler, + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */); + + mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); + waitForHandler(); + + verify(mListener, never()).onDisplayChanged(anyInt()); + verify(mListener2).onDisplayChanged(321); + + // Trigger the callback again even if the display info is not changed. + clearInvocations(mListener2); + mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); + waitForHandler(); + + verify(mListener2).onDisplayChanged(321); + + // No callback for non-existing display (no display info returned from IDisplayManager). + clearInvocations(mListener2); + mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(456); + waitForHandler(); + verify(mListener2, never()).onDisplayChanged(anyInt()); } private void waitForHandler() { diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index cc73ecee2146..35498b79ccb8 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2365,6 +2365,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, + "33989965": { + "message": " Met condition %s for #%d (%d left)", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "34106798": { "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?", "level": "VERBOSE", @@ -4483,6 +4489,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "2053743391": { + "message": " Add condition %s for #%d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "2060978050": { "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d", "level": "WARN", @@ -4543,6 +4555,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "2124732293": { + "message": "#%d: Met condition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "2128917433": { "message": "onProposedRotationChanged, rotation=%d", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 4eaa01309ab1..92c4de6490a3 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -27,6 +27,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.Size; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; @@ -614,6 +617,7 @@ public class Paint { mCompatScaling = mInvCompatScaling = 1; setTextLocales(LocaleList.getAdjustedDefault()); mColor = Color.pack(Color.BLACK); + resetElegantTextHeight(); } /** @@ -654,7 +658,7 @@ public class Paint { mBidiFlags = BIDI_DEFAULT_LTR; setTextLocales(LocaleList.getAdjustedDefault()); - setElegantTextHeight(false); + resetElegantTextHeight(); mFontFeatureSettings = null; mFontVariationSettings = null; @@ -1735,12 +1739,30 @@ public class Paint { /** * Get the elegant metrics flag. * + * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by + * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or + * later. + * * @return true if elegant metrics are enabled for text drawing. */ public boolean isElegantTextHeight() { - return nIsElegantTextHeight(mNativePaint); + int rawValue = nGetElegantTextHeight(mNativePaint); + switch (rawValue) { + case ELEGANT_TEXT_HEIGHT_DISABLED: + return false; + case ELEGANT_TEXT_HEIGHT_ENABLED: + return true; + case ELEGANT_TEXT_HEIGHT_UNSET: + default: + return com.android.text.flags.Flags.deprecateUiFonts(); + } } + // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp + private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1; + private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0; + private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1; + /** * Set the paint's elegant height metrics flag. This setting selects font * variants that have not been compacted to fit Latin-based vertical @@ -1749,7 +1771,29 @@ public class Paint { * @param elegant set the paint's elegant metrics flag for drawing text. */ public void setElegantTextHeight(boolean elegant) { - nSetElegantTextHeight(mNativePaint, elegant); + nSetElegantTextHeight(mNativePaint, + elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED); + } + + /** + * A change ID for deprecating UI fonts. + * + * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by + * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or + * later. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long DEPRECATE_UI_FONT = 279646685L; + + private void resetElegantTextHeight() { + if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) { + nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET); + } else { + nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED); + } } /** @@ -3660,9 +3704,9 @@ public class Paint { @CriticalNative private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText); @CriticalNative - private static native boolean nIsElegantTextHeight(long paintPtr); + private static native int nGetElegantTextHeight(long paintPtr); @CriticalNative - private static native void nSetElegantTextHeight(long paintPtr, boolean elegant); + private static native void nSetElegantTextHeight(long paintPtr, int elegant); @CriticalNative private static native float nGetTextSize(long paintPtr); @CriticalNative diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index dc1773bd7290..621958562b94 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -17,11 +17,17 @@ package android.graphics.text; import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN; +import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; +import android.os.Build; +import android.os.LocaleList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,6 +43,14 @@ import java.util.Objects; public final class LineBreakConfig { /** + * A feature ID for automatic line break word style. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long WORD_STYLE_AUTO = 280005585L; + + /** * No hyphenation preference is specified. * * <p> @@ -112,8 +126,11 @@ public final class LineBreakConfig { * </pre> * * <p> - * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text - * layout/rendering. + * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API + * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text + * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK + * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is + * used for text layout/rendering. */ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; @@ -154,10 +171,29 @@ public final class LineBreakConfig { @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int LINE_BREAK_STYLE_NO_BREAK = 4; + /** + * A special value for the line breaking style option. + * + * <p> + * The auto option for the line break style set the line break style based on the locale of the + * text rendering context. You can specify the context locale by + * {@link android.widget.TextView#setTextLocales(LocaleList)} or + * {@link android.graphics.Paint#setTextLocales(LocaleList)}. + * + * <p> + * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings: + * - If at least one locale in the locale list contains Japanese script, this option is + * equivalent to {@link #LINE_BREAK_STYLE_STRICT}. + * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}. + */ + @FlaggedApi(FLAG_WORD_STYLE_AUTO) + public static final int LINE_BREAK_STYLE_AUTO = 5; + /** @hide */ @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, - LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK + LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK, + LINE_BREAK_STYLE_AUTO }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} @@ -183,8 +219,11 @@ public final class LineBreakConfig { * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. * </pre> * - * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for - * text layout/rendering. + * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is + * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text + * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target + * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is + * used for text layout/rendering. */ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; @@ -204,9 +243,29 @@ public final class LineBreakConfig { */ public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; + /** + * A special value for the line breaking word style option. + * + * <p> + * The auto option for the line break word style does some heuristics based on locales and line + * count. + * + * <p> + * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings: + * - If at least one locale in the locale list contains Korean script, this option is equivalent + * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}. + * - If not, then if at least one locale in the locale list contains Japanese script, this + * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line + * count is less than 5 lines. + * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}. + */ + @FlaggedApi(FLAG_WORD_STYLE_AUTO) + public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; + /** @hide */ @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = { - LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED + LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED, + LINE_BREAK_WORD_STYLE_AUTO }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakWordStyle {} @@ -425,11 +484,13 @@ public final class LineBreakConfig { * @hide */ public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) { + final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) + ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE; if (config == null) { - return LINE_BREAK_STYLE_NONE; + return defaultStyle; } return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED - ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle; + ? defaultStyle : config.mLineBreakStyle; } /** @@ -451,11 +512,13 @@ public final class LineBreakConfig { */ public static @LineBreakWordStyle int getResolvedLineBreakWordStyle( @Nullable LineBreakConfig config) { + final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) + ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE; if (config == null) { - return LINE_BREAK_WORD_STYLE_NONE; + return defaultWordStyle; } return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED - ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle; + ? defaultWordStyle : config.mLineBreakWordStyle; } /** diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 7c0d0e37e28c..51c71b1fffb7 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -20,3 +20,10 @@ flag { description: "Enables desktop windowing" bug: "304778354" } + +flag { + name: "enable_split_contextual" + namespace: "multitasking" + description: "Enables invoking split contextually" + bug: "276361926" +} diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index ee9f070f6765..87e0b2867090 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -22,6 +22,7 @@ android:orientation="vertical"> <LinearLayout + android:id="@+id/app_info_pill" android:layout_width="match_parent" android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height" android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top" @@ -66,6 +67,7 @@ </LinearLayout> <LinearLayout + android:id="@+id/windowing_pill" android:layout_width="match_parent" android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height" android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin" @@ -116,6 +118,7 @@ </LinearLayout> <LinearLayout + android:id="@+id/more_actions_pill" android:layout_width="match_parent" android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height" android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin" 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 ac5ba51ec139..3660fa29e9e4 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 @@ -57,6 +57,7 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityNodeInfo; @@ -212,7 +213,8 @@ public class BubbleStackView extends FrameLayout private ExpandedViewAnimationController mExpandedViewAnimationController; private View mScrim; - private boolean mScrimAnimating; + @Nullable + private ViewPropertyAnimator mScrimAnimation; private View mManageMenuScrim; private FrameLayout mExpandedViewContainer; @@ -748,8 +750,8 @@ public class BubbleStackView extends FrameLayout float collapsed = -Math.min(dy, 0); mExpandedViewAnimationController.updateDrag((int) collapsed); - // Update scrim - if (!mScrimAnimating) { + // Update scrim if it's not animating already + if (mScrimAnimation == null) { mScrim.setAlpha(getScrimAlphaForDrag(collapsed)); } } @@ -768,8 +770,8 @@ public class BubbleStackView extends FrameLayout } else { mExpandedViewAnimationController.animateBackToExpanded(); - // Update scrim - if (!mScrimAnimating) { + // Update scrim if it's not animating already + if (mScrimAnimation == null) { showScrim(true, null /* runnable */); } } @@ -2237,26 +2239,27 @@ public class BubbleStackView extends FrameLayout private void showScrim(boolean show, Runnable after) { AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { @Override - public void onAnimationStart(Animator animation) { - mScrimAnimating = true; - } - - @Override public void onAnimationEnd(Animator animation) { - mScrimAnimating = false; + mScrimAnimation = null; if (after != null) { after.run(); } } }; + if (mScrimAnimation != null) { + // Cancel scrim animation if it animates + mScrimAnimation.cancel(); + } if (show) { - mScrim.animate() + mScrimAnimation = mScrim.animate(); + mScrimAnimation .setInterpolator(ALPHA_IN) .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA) .setListener(listener) .start(); } else { - mScrim.animate() + mScrimAnimation = mScrim.animate(); + mScrimAnimation .alpha(0f) .setInterpolator(ALPHA_OUT) .setListener(listener) 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 bb262d3df07f..3aed9ebc6c5e 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 @@ -455,7 +455,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Create and display handle menu window + * Create and display handle menu window. */ void createHandleMenu() { mHandleMenu = new HandleMenu.Builder(this) @@ -466,15 +466,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setLayoutId(mRelayoutParams.mLayoutResId) .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY) .setWindowingButtonsVisible(DesktopModeStatus.isEnabled()) + .setCaptionHeight(mResult.mCaptionHeight) .build(); + mWindowDecorViewHolder.onHandleMenuOpened(); mHandleMenu.show(); } /** - * Close the handle menu window + * Close the handle menu window. */ void closeHandleMenu() { if (!isHandleMenuActive()) return; + mWindowDecorViewHolder.onHandleMenuClosed(); mHandleMenu.close(); mHandleMenu = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java index 15f8f1cfadf2..6391518b5911 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java @@ -71,10 +71,13 @@ class HandleMenu { private int mMenuHeight; private int mMenuWidth; + private final int mCaptionHeight; + HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY, View.OnClickListener onClickListener, View.OnTouchListener onTouchListener, - Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) { + Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill, + int captionHeight) { mParentDecor = parentDecor; mContext = mParentDecor.mDecorWindowContext; mTaskInfo = mParentDecor.mTaskInfo; @@ -86,6 +89,7 @@ class HandleMenu { mAppIcon = appIcon; mAppName = appName; mShouldShowWindowingPill = shouldShowWindowingPill; + mCaptionHeight = captionHeight; loadHandleMenuDimensions(); updateHandleMenuPillPositions(); } @@ -98,6 +102,7 @@ class HandleMenu { ssg.addTransaction(t); ssg.markSyncReady(); setupHandleMenu(); + animateHandleMenu(); } private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { @@ -109,6 +114,21 @@ class HandleMenu { } /** + * Animates the appearance of the handle menu and its three pills. + */ + private void animateHandleMenu() { + final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView(); + final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView, + mMenuWidth, mCaptionHeight); + if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { + handleMenuAnimator.animateCaptionHandleExpandToOpen(); + } else { + handleMenuAnimator.animateOpen(); + } + } + + /** * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions * pill. */ @@ -322,6 +342,7 @@ class HandleMenu { private int mCaptionX; private int mCaptionY; private boolean mShowWindowingPill; + private int mCaptionHeight; Builder(@NonNull WindowDecoration parent) { @@ -364,9 +385,14 @@ class HandleMenu { return this; } + Builder setCaptionHeight(int captionHeight) { + mCaptionHeight = captionHeight; + return this; + } + HandleMenu build() { return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener, - mOnTouchListener, mAppIcon, mName, mShowWindowingPill); + mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt new file mode 100644 index 000000000000..531de1f79ea8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt @@ -0,0 +1,245 @@ +/* + * 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.windowdecor + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.view.View +import android.view.View.ALPHA +import android.view.View.SCALE_X +import android.view.View.SCALE_Y +import android.view.View.TRANSLATION_Y +import android.view.View.TRANSLATION_Z +import android.view.ViewGroup +import androidx.core.view.children +import com.android.wm.shell.R +import com.android.wm.shell.animation.Interpolators + +/** Animates the Handle Menu opening. */ +class HandleMenuAnimator( + private val handleMenu: View, + private val menuWidth: Int, + private val captionHeight: Float +) { + companion object { + private const val MENU_Y_TRANSLATION_DURATION: Long = 150 + private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150 + private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217 + private const val HEADER_ELEVATION_DURATION: Long = 83 + private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100 + private const val BODY_SCALE_DURATION: Long = 180 + private const val BODY_ALPHA_DURATION: Long = 150 + private const val BODY_ELEVATION_DURATION: Long = 83 + private const val BODY_CONTENT_ALPHA_DURATION: Long = 167 + + private const val ELEVATION_DELAY: Long = 33 + private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67 + private const val BODY_SCALE_DELAY: Long = 50 + private const val BODY_ALPHA_DELAY: Long = 133 + + private const val HALF_INITIAL_SCALE: Float = 0.5f + private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f + private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f + } + + private val animators: MutableList<Animator> = mutableListOf() + + private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill) + private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill) + private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill) + + /** Animates the opening of the handle menu. */ + fun animateOpen() { + prepareMenuForAnimation() + appInfoPillExpand() + animateAppInfoPill() + animateWindowingPill() + animateMoreActionsPill() + runAnimations() + } + + /** + * Animates the opening of the handle menu. The caption handle in full screen and split screen + * will expand until it assumes the shape of the app info pill. Then, the other two pills will + * appear. + */ + fun animateCaptionHandleExpandToOpen() { + prepareMenuForAnimation() + captionHandleExpandIntoAppInfoPill() + animateAppInfoPill() + animateWindowingPill() + animateMoreActionsPill() + runAnimations() + } + + /** + * Prepares the handle menu for animation. Presets the opacity of necessary menu components. + * Presets pivots of handle menu and body pills for scaling animation. + */ + private fun prepareMenuForAnimation() { + // Preset opacity + appInfoPill.children.forEach { it.alpha = 0f } + windowingPill.alpha = 0f + moreActionsPill.alpha = 0f + + // Setup pivots. + handleMenu.pivotX = menuWidth / 2f + handleMenu.pivotY = 0f + + windowingPill.pivotX = menuWidth / 2f + windowingPill.pivotY = appInfoPill.measuredHeight.toFloat() + + moreActionsPill.pivotX = menuWidth / 2f + moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat() + } + + private fun animateAppInfoPill() { + // Header Elevation Animation + animators += + ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply { + startDelay = ELEVATION_DELAY + duration = HEADER_ELEVATION_DURATION + } + + // Content Opacity Animation + appInfoPill.children.forEach { + animators += + ObjectAnimator.ofFloat(it, ALPHA, 1f).apply { + startDelay = HEADER_CONTENT_ALPHA_DELAY + duration = HEADER_CONTENT_ALPHA_DURATION + } + } + } + + private fun captionHandleExpandIntoAppInfoPill() { + // Header scaling animation + animators += + ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f) + .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION } + + animators += + ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f) + .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION } + + // Downward y-translation animation + val yStart: Float = -captionHeight / 2 + animators += + ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply { + duration = MENU_Y_TRANSLATION_DURATION + } + } + + private fun appInfoPillExpand() { + // Header scaling animation + animators += + ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { + duration = HEADER_FREEFORM_SCALE_DURATION + } + + animators += + ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { + duration = HEADER_FREEFORM_SCALE_DURATION + } + } + + private fun animateWindowingPill() { + // Windowing X & Y Scaling Animation + animators += + ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_DELAY + duration = BODY_SCALE_DURATION + } + + animators += + ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_DELAY + duration = BODY_SCALE_DURATION + } + + // Windowing Opacity Animation + animators += + ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_DELAY + duration = BODY_ALPHA_DURATION + } + + // Windowing Elevation Animation + animators += + ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply { + startDelay = ELEVATION_DELAY + duration = BODY_ELEVATION_DURATION + } + + // Windowing Content Opacity Animation + windowingPill.children.forEach { + animators += + ObjectAnimator.ofFloat(it, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_DELAY + duration = BODY_CONTENT_ALPHA_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN + } + } + } + + private fun animateMoreActionsPill() { + // More Actions X & Y Scaling Animation + animators += + ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_DELAY + duration = BODY_SCALE_DURATION + } + + animators += + ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_DELAY + duration = BODY_SCALE_DURATION + } + + // More Actions Opacity Animation + animators += + ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_DELAY + duration = BODY_ALPHA_DURATION + } + + // More Actions Elevation Animation + animators += + ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply { + startDelay = ELEVATION_DELAY + duration = BODY_ELEVATION_DURATION + } + + // More Actions Content Opacity Animation + moreActionsPill.children.forEach { + animators += + ObjectAnimator.ofFloat(it, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_DELAY + duration = BODY_CONTENT_ALPHA_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN + } + } + } + + /** Runs the list of animators concurrently. */ + private fun runAnimations() { + val animatorSet = AnimatorSet() + animatorSet.playTogether(animators) + animatorSet.start() + animators.clear() + } +} 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 044c0331282c..634b7558c7d8 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 @@ -269,10 +269,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .build(); } - final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); + outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight) .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) .show(mCaptionContainerSurface); @@ -283,7 +283,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionInsetsRect.set(taskBounds); if (mIsCaptionVisible) { mCaptionInsetsRect.bottom = - mCaptionInsetsRect.top + captionHeight + params.mCaptionY; + mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY; wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); wct.addInsetsSource(mTaskInfo.token, @@ -348,7 +348,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption view mCaptionWindowManager.setConfiguration(taskConfig); final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams(captionWidth, captionHeight, + new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); lp.setTitle("Caption of Task=" + mTaskInfo.taskId); @@ -569,6 +569,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } static class RelayoutResult<T extends View & TaskFocusStateConsumer> { + int mCaptionHeight; int mWidth; int mHeight; T mRootView; @@ -576,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> void reset() { mWidth = 0; mHeight = 0; + mCaptionHeight = 0; mRootView = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index 6b59ccec5148..400dec4df506 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -48,7 +48,6 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( } override fun bindData(taskInfo: RunningTaskInfo) { - val captionDrawable = captionView.background as GradientDrawable taskInfo.taskDescription?.statusBarColor?.let { captionDrawable.setColor(it) @@ -63,6 +62,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo)) } + override fun onHandleMenuOpened() {} + + override fun onHandleMenuClosed() {} + private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_app_name_light) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt index 9374ac95e83d..9dc86db4f59b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt @@ -1,11 +1,13 @@ package com.android.wm.shell.windowdecor.viewholder +import android.animation.ObjectAnimator import android.app.ActivityManager.RunningTaskInfo import android.content.res.ColorStateList import android.graphics.drawable.GradientDrawable import android.view.View import android.widget.ImageButton import com.android.wm.shell.R +import com.android.wm.shell.animation.Interpolators /** * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It @@ -17,6 +19,10 @@ internal class DesktopModeFocusedWindowDecorationViewHolder( onCaptionButtonClickListener: View.OnClickListener ) : DesktopModeWindowDecorationViewHolder(rootView) { + companion object { + private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100 + } + private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) @@ -35,6 +41,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder( captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo)) } + override fun onHandleMenuOpened() { + animateCaptionHandleAlpha(startValue = 0f, endValue = 1f) + } + + override fun onHandleMenuClosed() { + animateCaptionHandleAlpha(startValue = 1f, endValue = 0f) + } + private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_handle_bar_light) @@ -42,4 +56,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder( context.getColor(R.color.desktop_mode_caption_handle_bar_dark) } } + + /** Animate appearance/disappearance of caption handle as the handle menu is animated. */ + private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) { + val animator = + ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply { + duration = CAPTION_HANDLE_ANIMATION_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN + } + animator.start() + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt index 49e8d15dcc02..8b405f02ef29 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt @@ -35,4 +35,10 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) { } } ?: false } + + /** Callback when the handle menu is opened. */ + abstract fun onHandleMenuOpened() + + /** Callback when the handle menu is closed. */ + abstract fun onHandleMenuClosed() } diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index ffb329d9f8e6..00d049cde925 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -33,6 +33,14 @@ inline bool fix_double_underline() { #endif // __ANDROID__ } +inline bool deprecate_ui_fonts() { +#ifdef __ANDROID__ + return com_android_text_flags_deprecate_ui_fonts(); +#else + return true; +#endif // __ANDROID__ +} + } // namespace text_feature } // namespace android diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index bcfb4c89036d..7552b56d2ad6 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -16,12 +16,15 @@ #include "MinikinUtils.h" -#include <string> - #include <log/log.h> - +#include <minikin/FamilyVariant.h> #include <minikin/MeasuredText.h> #include <minikin/Measurement.h> + +#include <optional> +#include <string> + +#include "FeatureFlags.h" #include "Paint.h" #include "SkPathMeasure.h" #include "Typeface.h" @@ -43,9 +46,17 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.wordSpacing = paint->getWordSpacing(); minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font); minikinPaint.localeListId = paint->getMinikinLocaleListId(); - minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); + + const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); + if (familyVariant.has_value()) { + minikinPaint.familyVariant = familyVariant.value(); + } else { + minikinPaint.familyVariant = text_feature::deprecate_ui_fonts() + ? minikin::FamilyVariant::ELEGANT + : minikin::FamilyVariant::DEFAULT; + } return minikinPaint; } diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 4a8f3e10fc26..caffdfc907f7 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -94,9 +94,10 @@ public: uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; } + void resetFamilyVariant() { mFamilyVariant.reset(); } void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; } - minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; } + std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; } void setStartHyphenEdit(uint32_t startHyphen) { mHyphenEdit = minikin::packHyphenEdit( @@ -171,7 +172,7 @@ private: float mWordSpacing = 0; std::string mFontFeatureSettings; uint32_t mMinikinLocaleListId; - minikin::FamilyVariant mFamilyVariant; + std::optional<minikin::FamilyVariant> mFamilyVariant; uint32_t mHyphenEdit = 0; // The native Typeface object has the same lifetime of the Java Typeface // object. The Java Paint object holds a strong reference to the Java Typeface diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 14639456f13b..8c71d6fc7860 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -935,15 +935,39 @@ namespace PaintGlue { obj->setMinikinLocaleListId(minikinLocaleListId); } - static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { + // Note: Following three values must be equal to the ones in Java file: Paint.java. + constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1; + constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0; + constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1; + + static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { Paint* obj = reinterpret_cast<Paint*>(paintHandle); - return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT; + const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant(); + if (familyVariant.has_value()) { + if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) { + return ELEGANT_TEXT_HEIGHT_ENABLED; + } else { + return ELEGANT_TEXT_HEIGHT_DISABLED; + } + } else { + return ELEGANT_TEXT_HEIGHT_UNSET; + } } - static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) { + static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) { Paint* obj = reinterpret_cast<Paint*>(paintHandle); - obj->setFamilyVariant( - aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT); + switch (value) { + case ELEGANT_TEXT_HEIGHT_ENABLED: + obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT); + return; + case ELEGANT_TEXT_HEIGHT_DISABLED: + obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT); + return; + case ELEGANT_TEXT_HEIGHT_UNSET: + default: + obj->resetFamilyVariant(); + return; + } } static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { @@ -1178,8 +1202,8 @@ static const JNINativeMethod methods[] = { {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign}, {"nSetTextLocalesByMinikinLocaleListId", "(JI)V", (void*)PaintGlue::setTextLocalesByMinikinLocaleListId}, - {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight}, - {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight}, + {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight}, + {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight}, {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize}, {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize}, {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX}, diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index af3c295b8d6c..5a274353f68e 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -68,7 +68,7 @@ public final class AudioDeviceAttributes implements Parcelable { /** * The unique address of the device. Some devices don't have addresses, only an empty string. */ - private final @NonNull String mAddress; + private @NonNull String mAddress; /** * The non-unique name of the device. Some devices don't have names, only an empty string. * Should not be used as a unique identifier for a device. @@ -188,6 +188,21 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide + * Copy Constructor. + * @param ada the copied AudioDeviceAttributes + */ + public AudioDeviceAttributes(AudioDeviceAttributes ada) { + mRole = ada.getRole(); + mType = ada.getType(); + mAddress = ada.getAddress(); + mName = ada.getName(); + mNativeType = ada.getInternalType(); + mAudioProfiles = ada.getAudioProfiles(); + mAudioDescriptors = ada.getAudioDescriptors(); + } + + /** + * @hide * Returns the role of a device * @return the role */ @@ -218,6 +233,15 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide + * Sets the device address. Only used by audio service. + */ + public void setAddress(@NonNull String address) { + Objects.requireNonNull(address); + mAddress = address; + } + + /** + * @hide * Returns the name of the audio device, or an empty string for devices without one * @return the device name */ diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5b880797b7fd..9ad5c3e79543 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -20,6 +20,8 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; +import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -1060,8 +1062,17 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + if (autoPublicVolumeApiHardening()) { + final IAudioService service = getService(); + try { + service.adjustVolume(direction, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + } } /** @@ -1090,8 +1101,17 @@ public class AudioManager { */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + if (autoPublicVolumeApiHardening()) { + final IAudioService service = getService(); + try { + service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + } } /** @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 0e7718b060bc..8584dbc62ef9 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -498,6 +498,10 @@ interface IAudioService { in String packageName, int uid, int pid, in UserHandle userHandle, int targetSdkVersion); + oneway void adjustVolume(int direction, int flags); + + oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); + boolean isMusicActive(in boolean remotely); int getDeviceMaskForStream(in int streamType); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 96876747c082..6eaabbb389c2 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1098,19 +1098,6 @@ <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] --> <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging --> <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string> <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging --> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java index 660090ddef2d..19005755ef58 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java @@ -19,14 +19,19 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.util.Log; @@ -36,6 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** * HapClientProfile handles the Bluetooth HAP service client role. @@ -118,7 +124,52 @@ public class HapClientProfile implements LocalBluetoothProfile { } /** - * Get hearing aid devices matching connection states{ + * Registers a {@link BluetoothHapClient.Callback} that will be invoked during the + * operation of this profile. + * + * Repeated registration of the same <var>callback</var> object after the first call to this + * method will result with IllegalArgumentException being thrown, even when the + * <var>executor</var> is different. API caller would have to call + * {@link #unregisterCallback(BluetoothHapClient.Callback)} with the same callback object + * before registering it again. + * + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link BluetoothHapClient.Callback} + * @throws NullPointerException if a null executor, or callback is given, or + * IllegalArgumentException if the same <var>callback</var> is already registered. + * @hide + */ + public void registerCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull BluetoothHapClient.Callback callback) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot register callback."); + return; + } + mService.registerCallback(executor, callback); + } + + /** + * Unregisters the specified {@link BluetoothHapClient.Callback}. + * <p>The same {@link BluetoothHapClient.Callback} object used when calling + * {@link #registerCallback(Executor, BluetoothHapClient.Callback)} must be used. + * + * <p>Callbacks are automatically unregistered when application process goes away + * + * @param callback user implementation of the {@link BluetoothHapClient.Callback} + * @throws NullPointerException when callback is null or IllegalArgumentException when no + * callback is registered + * @hide + */ + public void unregisterCallback(@NonNull BluetoothHapClient.Callback callback) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot unregister callback."); + return; + } + mService.unregisterCallback(callback); + } + + /** + * Gets hearing aid devices matching connection states{ * {@code BluetoothProfile.STATE_CONNECTED}, * {@code BluetoothProfile.STATE_CONNECTING}, * {@code BluetoothProfile.STATE_DISCONNECTING}} @@ -133,7 +184,7 @@ public class HapClientProfile implements LocalBluetoothProfile { } /** - * Get hearing aid devices matching connection states{ + * Gets hearing aid devices matching connection states{ * {@code BluetoothProfile.STATE_DISCONNECTED}, * {@code BluetoothProfile.STATE_CONNECTED}, * {@code BluetoothProfile.STATE_CONNECTING}, @@ -222,6 +273,270 @@ public class HapClientProfile implements LocalBluetoothProfile { return mService.supportsWritablePresets(device); } + + /** + * Gets the group identifier, which can be used in the group related part of the API. + * + * <p>Users are expected to get group identifier for each of the connected device to discover + * the device grouping. This allows them to make an informed decision which devices can be + * controlled by single group API call and which require individual device calls. + * + * <p>Note that some binaural HA devices may not support group operations, therefore are not + * considered a valid HAP group. In such case -1 is returned even if such device is a valid Le + * Audio Coordinated Set member. + * + * @param device is the device for which we want to get the hap group identifier + * @return valid group identifier or -1 + * @hide + */ + public int getHapGroup(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get hap group."); + return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; + } + return mService.getHapGroup(device); + } + + /** + * Gets the currently active preset for a HA device. + * + * @param device is the device for which we want to set the active preset + * @return active preset index or {@link BluetoothHapClient#PRESET_INDEX_UNAVAILABLE} if the + * device is not connected. + * @hide + */ + public int getActivePresetIndex(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get active preset index."); + return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; + } + return mService.getActivePresetIndex(device); + } + + /** + * Gets the currently active preset info for a remote device. + * + * @param device is the device for which we want to get the preset name + * @return currently active preset info if selected, null if preset info is not available for + * the remote device + * @hide + */ + @Nullable + public BluetoothHapPresetInfo getActivePresetInfo(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get active preset info."); + return null; + } + return mService.getActivePresetInfo(device); + } + + /** + * Selects the currently active preset for a HA device + * + * <p>On success, + * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be + * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, + * {@link BluetoothHapClient.Callback#onPresetSelectionFailed(BluetoothDevice, int)} will be + * called. + * + * @param device is the device for which we want to set the active preset + * @param presetIndex is an index of one of the available presets + * @hide + */ + public void selectPreset(@NonNull BluetoothDevice device, int presetIndex) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot select preset."); + return; + } + mService.selectPreset(device, presetIndex); + } + + + /** + * Selects the currently active preset for a Hearing Aid device group. + * + * <p>This group call may replace multiple device calls if those are part of the valid HAS + * group. Note that binaural HA devices may or may not support group. + * + * <p>On success, + * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be + * called for each device within the group with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, + * {@link BluetoothHapClient.Callback#onPresetSelectionForGroupFailed(int, int)} will be + * called for the group. + * + * @param groupId is the device group identifier for which want to set the active preset + * @param presetIndex is an index of one of the available presets + * @hide + */ + public void selectPresetForGroup(int groupId, int presetIndex) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot select preset for group."); + return; + } + mService.selectPresetForGroup(groupId, presetIndex); + } + + /** + * Sets the next preset as a currently active preset for a HA device + * + * <p>Note that the meaning of 'next' is HA device implementation specific and does not + * necessarily mean a higher preset index. + * + * @param device is the device for which we want to set the active preset + * @hide + */ + public void switchToNextPreset(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset."); + return; + } + mService.switchToNextPreset(device); + } + + + /** + * Sets the next preset as a currently active preset for a HA device group + * + * <p>Note that the meaning of 'next' is HA device implementation specific and does not + * necessarily mean a higher preset index. + * + * <p>This group call may replace multiple device calls if those are part of the valid HAS + * group. Note that binaural HA devices may or may not support group. + * + * @param groupId is the device group identifier for which want to set the active preset + * @hide + */ + public void switchToNextPresetForGroup(int groupId) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset for group."); + return; + } + mService.switchToNextPresetForGroup(groupId); + } + + /** + * Sets the previous preset as a currently active preset for a HA device. + * + * <p>Note that the meaning of 'previous' is HA device implementation specific and does not + * necessarily mean a lower preset index. + * + * @param device is the device for which we want to set the active preset + * @hide + */ + public void switchToPreviousPreset(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset."); + return; + } + mService.switchToPreviousPreset(device); + } + + + /** + * Sets the next preset as a currently active preset for a HA device group + * + * <p>Note that the meaning of 'next' is HA device implementation specific and does not + * necessarily mean a higher preset index. + * + * <p>This group call may replace multiple device calls if those are part of the valid HAS + * group. Note that binaural HA devices may or may not support group. + * + * @param groupId is the device group identifier for which want to set the active preset + * @hide + */ + public void switchToPreviousPresetForGroup(int groupId) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset for " + + "group."); + return; + } + mService.switchToPreviousPresetForGroup(groupId); + } + + /** + * Requests the preset info + * + * @param device is the device for which we want to get the preset name + * @param presetIndex is an index of one of the available presets + * @return preset info + * @hide + */ + public BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device, int presetIndex) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get preset info."); + return null; + } + return mService.getPresetInfo(device, presetIndex); + } + + /** + * Gets all preset info for a particular device + * + * @param device is the device for which we want to get all presets info + * @return a list of all known preset info + * @hide + */ + @NonNull + public List<BluetoothHapPresetInfo> getAllPresetInfo(@NonNull BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get all preset info."); + return new ArrayList<>(); + } + return mService.getAllPresetInfo(device); + } + + /** + * Sets the preset name for a particular device + * + * <p>Note that the name length is restricted to 40 characters. + * + * <p>On success, + * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a + * new name will be called and reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, + * {@link BluetoothHapClient.Callback#onSetPresetNameFailed(BluetoothDevice, int)} will be + * called. + * + * @param device is the device for which we want to get the preset name + * @param presetIndex is an index of one of the available presets + * @param name is a new name for a preset, maximum length is 40 characters + * @hide + */ + public void setPresetName(@NonNull BluetoothDevice device, int presetIndex, + @NonNull String name) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot set preset name."); + return; + } + mService.setPresetName(device, presetIndex, name); + } + + /** + * Sets the name for a hearing aid preset. + * + * <p>Note that the name length is restricted to 40 characters. + * + * <p>On success, + * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a + * new name will be called for each device within the group with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, + * {@link BluetoothHapClient.Callback#onSetPresetNameForGroupFailed(int, int)} will be invoked + * + * @param groupId is the device group identifier + * @param presetIndex is an index of one of the available presets + * @param name is a new name for a preset, maximum length is 40 characters + * @hide + */ + public void setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot set preset name for group."); + return; + } + mService.setPresetNameForGroup(groupId, presetIndex, name); + } + + @Override public boolean accessProfileEnabled() { return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java index 673f2438de8a..22726549ce05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java @@ -44,45 +44,6 @@ public class PowerUtil { private static final long ONE_MIN_MILLIS = TimeUnit.MINUTES.toMillis(1); /** - * This method produces the text used in various places throughout the system to describe the - * remaining battery life of the phone in a consistent manner. - * - * @param context - * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds. - * @param percentageString An optional percentage of battery remaining string. - * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation. - * @return a properly formatted and localized string describing how much time remains - * before the battery runs out. - */ - public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs, - @Nullable String percentageString, boolean basedOnUsage) { - if (drainTimeMs > 0) { - if (drainTimeMs <= SEVEN_MINUTES_MILLIS) { - // show a imminent shutdown warning if less than 7 minutes remain - return getShutdownImminentString(context, percentageString); - } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) { - // show a less than 15 min remaining warning if appropriate - CharSequence timeString = StringUtil.formatElapsedTime(context, - FIFTEEN_MINUTES_MILLIS, - false /* withSeconds */, false /* collapseTimeUnit */); - return getUnderFifteenString(context, timeString, percentageString); - } else if (drainTimeMs >= TWO_DAYS_MILLIS) { - // just say more than two day if over 48 hours - return getMoreThanTwoDaysString(context, percentageString); - } else if (drainTimeMs >= ONE_DAY_MILLIS) { - // show remaining days & hours if more than a day - return getMoreThanOneDayString(context, drainTimeMs, - percentageString, basedOnUsage); - } else { - // show the time of day we think you'll run out - return getRegularTimeRemainingString(context, drainTimeMs, - percentageString, basedOnUsage); - } - } - return null; - } - - /** * Method to produce a shortened string describing the remaining battery. Suitable for Quick * Settings and other areas where space is constrained. * @@ -128,14 +89,6 @@ public class PowerUtil { } } - private static String getShutdownImminentString(Context context, String percentageString) { - return TextUtils.isEmpty(percentageString) - ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent) - : context.getString( - R.string.power_remaining_duration_shutdown_imminent, - percentageString); - } - private static String getUnderFifteenString(Context context, CharSequence timeString, String percentageString) { return TextUtils.isEmpty(percentageString) @@ -268,4 +221,4 @@ public class PowerUtil { return time - remainder + multiple; } } -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java index 03a792a6d250..7e3263bf8fae 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java @@ -26,10 +26,12 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; +import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; @@ -47,11 +49,16 @@ import org.robolectric.shadow.api.Shadow; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class HapClientProfileTest { + private static final int TEST_GROUP_ID = 1; + private static final int TEST_PRESET_INDEX = 1; + private static final String TEST_DEVICE_NAME = "test_device"; + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -63,6 +70,8 @@ public class HapClientProfileTest { private BluetoothDevice mBluetoothDevice; @Mock private BluetoothHapClient mService; + @Mock + private BluetoothHapPresetInfo mPresetInfo; private final Context mContext = ApplicationProvider.getApplicationContext(); private BluetoothProfile.ServiceListener mServiceListener; @@ -206,4 +215,275 @@ public class HapClientProfileTest { assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size()); } + + /** + * Verify registerCallback() call is correctly delegated to {@link BluetoothHapClient} service. + */ + @Test + public void registerCallback_verifyIsCalled() { + final Executor executor = (command -> new Thread(command).start()); + final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() { + @Override + public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, + int reason) { + + } + + @Override + public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) { + + } + + @Override + public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { + + } + + @Override + public void onPresetInfoChanged(@NonNull BluetoothDevice device, + @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) { + + } + + @Override + public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) { + + } + + @Override + public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { + + } + }; + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.registerCallback(executor, callback); + + verify(mService).registerCallback(executor, callback); + } + + /** + * Verify unregisterCallback() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void unregisterCallback_verifyIsCalled() { + final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() { + @Override + public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, + int reason) { + + } + + @Override + public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) { + + } + + @Override + public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { + + } + + @Override + public void onPresetInfoChanged(@NonNull BluetoothDevice device, + @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) { + + } + + @Override + public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) { + + } + + @Override + public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { + + } + }; + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.unregisterCallback(callback); + + verify(mService).unregisterCallback(callback); + } + + /** + * Verify getHapGroup() call is correctly delegated to {@link BluetoothHapClient} service + * and return correct value. + */ + @Test + public void getHapGroup_verifyIsCalledAndReturnCorrectValue() { + when(mService.getHapGroup(mBluetoothDevice)).thenReturn(TEST_GROUP_ID); + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + final int groupId = mProfile.getHapGroup(mBluetoothDevice); + + verify(mService).getHapGroup(mBluetoothDevice); + assertThat(groupId).isEqualTo(TEST_GROUP_ID); + } + + /** + * Verify getActivePresetIndex() call is correctly delegated to {@link BluetoothHapClient} + * service and return correct index. + */ + @Test + public void getActivePresetIndex_verifyIsCalledAndReturnCorrectValue() { + when(mService.getActivePresetIndex(mBluetoothDevice)).thenReturn(TEST_PRESET_INDEX); + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + final int activeIndex = mProfile.getActivePresetIndex(mBluetoothDevice); + + verify(mService).getActivePresetIndex(mBluetoothDevice); + assertThat(activeIndex).isEqualTo(TEST_PRESET_INDEX); + } + + /** + * Verify getActivePresetInfo() call is correctly delegated to {@link BluetoothHapClient} + * service and return correct object. + */ + @Test + public void getActivePresetInfo_verifyIsCalledAndReturnCorrectObject() { + when(mService.getActivePresetInfo(mBluetoothDevice)).thenReturn(mPresetInfo); + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + final BluetoothHapPresetInfo activeInfo = mProfile.getActivePresetInfo(mBluetoothDevice); + + verify(mService).getActivePresetInfo(mBluetoothDevice); + assertThat(activeInfo).isEqualTo(mPresetInfo); + } + + /** + * Verify selectPreset() call is correctly delegated to {@link BluetoothHapClient} service. + */ + @Test + public void selectPreset_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + + verify(mService).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + } + + /** + * Verify selectPresetForGroup() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void selectPresetForGroup_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX); + + verify(mService).selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX); + } + + /** + * Verify switchToNextPreset() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void switchToNextPreset_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.switchToNextPreset(mBluetoothDevice); + + verify(mService).switchToNextPreset(mBluetoothDevice); + } + + /** + * Verify switchToNextPresetForGroup() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void switchToNextPresetForGroup_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.switchToNextPresetForGroup(TEST_GROUP_ID); + + verify(mService).switchToNextPresetForGroup(TEST_GROUP_ID); + } + + /** + * Verify switchToPreviousPreset() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void switchToPreviousPreset_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.switchToPreviousPreset(mBluetoothDevice); + + verify(mService).switchToPreviousPreset(mBluetoothDevice); + } + + /** + * Verify switchToPreviousPresetForGroup() call is correctly delegated to + * {@link BluetoothHapClient} service. + */ + @Test + public void switchToPreviousPresetForGroup_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.switchToPreviousPresetForGroup(TEST_GROUP_ID); + + verify(mService).switchToPreviousPresetForGroup(TEST_GROUP_ID); + } + + /** + * Verify getPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service and + * return correct object. + */ + @Test + public void getPresetInfo_verifyIsCalledAndReturnCorrectObject() { + when(mService.getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX)).thenReturn(mPresetInfo); + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + final BluetoothHapPresetInfo info = mProfile.getPresetInfo(mBluetoothDevice, + TEST_PRESET_INDEX); + + verify(mService).getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX); + assertThat(info).isEqualTo(mPresetInfo); + } + + /** + * Verify getAllPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service + * and return correct list. + */ + @Test + public void getAllPresetInfo_verifyIsCalledAndReturnCorrectList() { + final List<BluetoothHapPresetInfo> testList = Arrays.asList(mPresetInfo, mPresetInfo); + when(mService.getAllPresetInfo(mBluetoothDevice)).thenReturn(testList); + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + final List<BluetoothHapPresetInfo> infoList = mProfile.getAllPresetInfo(mBluetoothDevice); + + verify(mService).getAllPresetInfo(mBluetoothDevice); + assertThat(infoList.size()).isEqualTo(testList.size()); + } + + /** + * Verify setPresetName() call is correctly delegated to {@link BluetoothHapClient} service. + */ + @Test + public void setPresetName_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME); + + verify(mService).setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME); + } + + /** + * Verify setPresetNameForGroup() call is correctly delegated to {@link BluetoothHapClient} + * service. + */ + @Test + public void setPresetNameForGroup_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService); + + mProfile.setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME); + + verify(mService).setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index ae542062ce50..2e7905f2e1e4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -59,155 +59,6 @@ public class PowerUtilTest { } @Test - public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - TEST_BATTERY_LEVEL_10, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - TEST_BATTERY_LEVEL_10, - false /* basedOnUsage */); - - // We only add special mention for the long string - // ex: Will last about 1:15 PM based on your usage (10%) - assertThat(info).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + ENHANCED_SUFFIX - + PERCENTAGE_REGEX)); - // shortened string should not have extra text - // ex: Will last about 1:15 PM (10%) - assertThat(info2).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + PERCENTAGE_REGEX)); - } - - @Test - public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_noPercentage() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only have % when it is provided - // ex: Will last about 1:15 PM based on your usage - assertThat(info).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + ENHANCED_SUFFIX - + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage - // shortened string should not have extra text - // ex: Will last about 1:15 PM - assertThat(info2).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage - } - - @Test - public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - FIVE_MINUTES_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - FIVE_MINUTES_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - - // additional battery percentage in this string - assertThat(info.contains("may shut down soon (10%)")).isTrue(); - // shortened string should not have percentage - assertThat(info2.contains("may shut down soon")).isTrue(); - } - - @Test - public void testGetBatteryRemainingStringFormatted_betweenSevenAndFifteenMinutes_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_MINUTES_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_MINUTES_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - - // shortened string should not have percentage - assertThat(info).isEqualTo("Less than 15 min left"); - // Add percentage to string when provided - assertThat(info2).isEqualTo("Less than 15 min left (10%)"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_betweenOneAndTwoDays_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - false /* basedOnUsage */); - String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS + TEN_MINUTES_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only add special mention for the long string - assertThat(info).isEqualTo("About 1 day, 6 hr left based on your usage"); - // shortened string should not have extra text - assertThat(info2).isEqualTo("About 1 day, 6 hr left (10%)"); - // present 2 time unit at most - assertThat(info3).isEqualTo("About 1 day, 6 hr left"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_lessThanOneDay_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - false /* basedOnUsage */); - String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS + TEN_MINUTES_MILLIS + TEN_SEC_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only add special mention for the long string - assertThat(info).isEqualTo("About 10 hr left based on your usage"); - // shortened string should not have extra text - assertThat(info2).isEqualTo("About 10 hr left (10%)"); - // present 2 time unit at most - assertThat(info3).isEqualTo("About 10 hr, 10 min left"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_moreThanTwoDays_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THREE_DAYS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THREE_DAYS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - - // shortened string should not have percentage - assertThat(info).isEqualTo("More than 2 days left"); - // Add percentage to string when provided - assertThat(info2).isEqualTo("More than 2 days left (10%)"); - } - - @Test public void getBatteryTipStringFormatted_moreThanOneDay_usesCorrectString() { String info = PowerUtil.getBatteryTipStringFormatted(mContext, THREE_DAYS_MILLIS); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index c6e9c03d5968..603e19fac268 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -220,6 +220,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, Settings.Secure.NOTIFICATION_BUBBLES, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 0727677b1a72..5457c35f9696 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -309,6 +309,9 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, + BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE, new InclusiveIntegerRangeValidator( Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index f06b31c4e965..7db189bc17e9 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1850,6 +1850,10 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, + SecureSettingsProto.Accessibility + .ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE, SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_GESTURE); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4e2fad0bece2..95d7039859b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -40,7 +40,6 @@ import static com.android.providers.settings.SettingsState.getUserIdFromKey; import static com.android.providers.settings.SettingsState.isConfigSettingsKey; import static com.android.providers.settings.SettingsState.isGlobalSettingsKey; import static com.android.providers.settings.SettingsState.isSecureSettingsKey; -import static com.android.providers.settings.SettingsState.isSsaidSettingsKey; import static com.android.providers.settings.SettingsState.isSystemSettingsKey; import static com.android.providers.settings.SettingsState.makeKey; @@ -412,6 +411,9 @@ public class SettingsProvider extends ContentProvider { SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext()); synchronized (mLock) { mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked(); + for (UserInfo user : mUserManager.getAliveUsers()) { + mSettingsRegistry.ensureSettingsForUserLocked(user.id); + } mSettingsRegistry.syncSsaidTableOnStartLocked(); } mHandler.post(() -> { @@ -427,65 +429,53 @@ public class SettingsProvider extends ContentProvider { public Bundle call(String method, String name, Bundle args) { final int requestingUserId = getRequestingUserId(args); switch (method) { - case Settings.CALL_METHOD_GET_CONFIG: { + case Settings.CALL_METHOD_GET_CONFIG -> { Setting setting = getConfigSetting(name); return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_GLOBAL: { + case Settings.CALL_METHOD_GET_GLOBAL -> { Setting setting = getGlobalSetting(name); return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_SECURE: { + case Settings.CALL_METHOD_GET_SECURE -> { Setting setting = getSecureSetting(name, requestingUserId); return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_SYSTEM: { + case Settings.CALL_METHOD_GET_SYSTEM -> { Setting setting = getSystemSetting(name, requestingUserId); return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_PUT_CONFIG: { + case Settings.CALL_METHOD_PUT_CONFIG -> { String value = getSettingValue(args); final boolean makeDefault = getSettingMakeDefault(args); insertConfigSetting(name, value, makeDefault); - break; } - - case Settings.CALL_METHOD_PUT_GLOBAL: { + case Settings.CALL_METHOD_PUT_GLOBAL -> { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); final boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_PUT_SECURE: { + case Settings.CALL_METHOD_PUT_SECURE -> { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); final boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_PUT_SYSTEM: { + case Settings.CALL_METHOD_PUT_SYSTEM -> { String value = getSettingValue(args); boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertSystemSetting(name, value, requestingUserId, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_SET_ALL_CONFIG: { + case Settings.CALL_METHOD_SET_ALL_CONFIG -> { String prefix = getSettingPrefix(args); Map<String, String> flags = getSettingFlags(args); Bundle result = new Bundle(); @@ -493,120 +483,96 @@ public class SettingsProvider extends ContentProvider { setAllConfigSettings(prefix, flags)); return result; } - - case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG: { + case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG -> { final int mode = getSyncDisabledMode(args); setSyncDisabledModeConfig(mode); - break; } - - case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG: { + case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG -> { Bundle result = new Bundle(); result.putInt(Settings.KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN, getSyncDisabledModeConfig()); return result; } - - case Settings.CALL_METHOD_RESET_CONFIG: { + case Settings.CALL_METHOD_RESET_CONFIG -> { final int mode = getResetModeEnforcingPermission(args); String prefix = getSettingPrefix(args); resetConfigSetting(mode, prefix); - break; } - - case Settings.CALL_METHOD_RESET_GLOBAL: { + case Settings.CALL_METHOD_RESET_GLOBAL -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetGlobalSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_RESET_SECURE: { + case Settings.CALL_METHOD_RESET_SECURE -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetSecureSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_RESET_SYSTEM: { + case Settings.CALL_METHOD_RESET_SYSTEM -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetSystemSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_DELETE_CONFIG: { - int rows = deleteConfigSetting(name) ? 1 : 0; + case Settings.CALL_METHOD_DELETE_CONFIG -> { + int rows = deleteConfigSetting(name) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_GLOBAL: { + case Settings.CALL_METHOD_DELETE_GLOBAL -> { int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_SECURE: { + case Settings.CALL_METHOD_DELETE_SECURE -> { int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_SYSTEM: { + case Settings.CALL_METHOD_DELETE_SYSTEM -> { int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_LIST_CONFIG: { + case Settings.CALL_METHOD_LIST_CONFIG -> { String prefix = getSettingPrefix(args); Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix), isTrackingGeneration(args)); reportDeviceConfigAccess(prefix); return result; } - - case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: { + case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> { RemoteCallback callback = args.getParcelable( Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); setMonitorCallback(callback); - break; } - - case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG: { + case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG -> { clearMonitorCallback(); - break; } - - case Settings.CALL_METHOD_LIST_GLOBAL: { + case Settings.CALL_METHOD_LIST_GLOBAL -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllGlobalSettings(null))); return result; } - - case Settings.CALL_METHOD_LIST_SECURE: { + case Settings.CALL_METHOD_LIST_SECURE -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllSecureSettings(requestingUserId, null))); return result; } - - case Settings.CALL_METHOD_LIST_SYSTEM: { + case Settings.CALL_METHOD_LIST_SYSTEM -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllSystemSettings(requestingUserId, null))); return result; } - - default: { + default -> { Slog.w(LOG_TAG, "call() with invalid method: " + method); - } break; + } } return null; @@ -638,7 +604,7 @@ public class SettingsProvider extends ContentProvider { } switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { if (args.name != null) { Setting setting = getGlobalSetting(args.name); return packageSettingForQuery(setting, normalizedProjection); @@ -646,8 +612,7 @@ public class SettingsProvider extends ContentProvider { return getAllGlobalSettings(projection); } } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); if (args.name != null) { Setting setting = getSecureSetting(args.name, userId); @@ -656,8 +621,7 @@ public class SettingsProvider extends ContentProvider { return getAllSecureSettings(userId, projection); } } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); if (args.name != null) { Setting setting = getSystemSetting(args.name, userId); @@ -666,8 +630,7 @@ public class SettingsProvider extends ContentProvider { return getAllSystemSettings(userId, projection); } } - - default: { + default -> { throw new IllegalArgumentException("Invalid Uri path:" + uri); } } @@ -708,30 +671,27 @@ public class SettingsProvider extends ContentProvider { String value = values.getAsString(Settings.Secure.VALUE); switch (table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { if (insertGlobalSetting(name, value, null, false, UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) { - return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name); + return Uri.withAppendedPath(Global.CONTENT_URI, name); } - } break; - - case TABLE_SECURE: { + } + case TABLE_SECURE -> { if (insertSecureSetting(name, value, null, false, UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) { - return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); + return Uri.withAppendedPath(Secure.CONTENT_URI, name); } - } break; - - case TABLE_SYSTEM: { + } + case TABLE_SYSTEM -> { if (insertSystemSetting(name, value, UserHandle.getCallingUserId(), /* overridableByRestore */ false)) { return Uri.withAppendedPath(Settings.System.CONTENT_URI, name); } - } break; - - default: { + } + default -> { throw new IllegalArgumentException("Bad Uri path:" + uri); } } @@ -775,22 +735,19 @@ public class SettingsProvider extends ContentProvider { } switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { final int userId = UserHandle.getCallingUserId(); return deleteGlobalSetting(args.name, userId, false) ? 1 : 0; } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); return deleteSecureSetting(args.name, userId, false) ? 1 : 0; } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); return deleteSystemSetting(args.name, userId) ? 1 : 0; } - - default: { + default -> { throw new IllegalArgumentException("Bad Uri path:" + uri); } } @@ -816,24 +773,21 @@ public class SettingsProvider extends ContentProvider { String value = values.getAsString(Settings.Secure.VALUE); switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { final int userId = UserHandle.getCallingUserId(); return updateGlobalSetting(args.name, value, null, false, userId, false) ? 1 : 0; } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); return updateSecureSetting(args.name, value, null, false, userId, false) ? 1 : 0; } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); return updateSystemSetting(args.name, value, userId) ? 1 : 0; } - - default: { + default -> { throw new IllegalArgumentException("Invalid Uri path:" + uri); } } @@ -1034,27 +988,38 @@ public class SettingsProvider extends ContentProvider { private void registerBroadcastReceivers() { IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_ADDED); userFilter.addAction(Intent.ACTION_USER_REMOVED); userFilter.addAction(Intent.ACTION_USER_STOPPED); getContext().registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent.getAction() == null) { + return; + } final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_SYSTEM); + UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + return; + } switch (intent.getAction()) { - case Intent.ACTION_USER_REMOVED: { + case Intent.ACTION_USER_ADDED -> { + synchronized (mLock) { + mSettingsRegistry.ensureSettingsForUserLocked(userId); + } + } + case Intent.ACTION_USER_REMOVED -> { synchronized (mLock) { mSettingsRegistry.removeUserStateLocked(userId, true); } - } break; - - case Intent.ACTION_USER_STOPPED: { + } + case Intent.ACTION_USER_STOPPED -> { synchronized (mLock) { mSettingsRegistry.removeUserStateLocked(userId, false); } - } break; + } } } }, userFilter); @@ -1350,26 +1315,24 @@ public class SettingsProvider extends ContentProvider { // Perform the mutation. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, value, null, makeDefault, true, callingPackage, false, null, /* overrideableByRestore */ false); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, false, null); } - - case MUTATION_OPERATION_RESET: { + case MUTATION_OPERATION_RESET -> { enforceDeviceConfigWritePermission(getContext(), getAllConfigFlags(prefix).keySet()); - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, callingPackage, mode, null, prefix); - } return true; + } } } @@ -1523,7 +1486,7 @@ public class SettingsProvider extends ContentProvider { enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // If this is a setting that is currently restricted for this user, do not allow // unrestricting changes. @@ -1536,28 +1499,25 @@ public class SettingsProvider extends ContentProvider { // Perform the mutation. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS, overrideableByRestore); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS); } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS); } - - case MUTATION_OPERATION_RESET: { - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL, + case MUTATION_OPERATION_RESET -> { + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, callingPackage, mode, tag); - } return true; + } } } @@ -1580,12 +1540,12 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId); // The relevant "calling package" userId will be the owning userId for some // profiles, and we can't do the lookup inside our [lock held] loop, so work out // up front who the effective "new SSAID" user ID for that settings name will be. - final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, + final int ssaidUserId = resolveOwningUserIdForSecureSetting(callingUserId, Settings.Secure.ANDROID_ID); final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId); @@ -1600,7 +1560,7 @@ public class SettingsProvider extends ContentProvider { for (int i = 0; i < nameCount; i++) { String name = names.get(i); // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); if (!isSecureSettingAccessible(name)) { @@ -1638,13 +1598,13 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // Ensure the caller can access the setting. enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId()); // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name); + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); if (!isSecureSettingAccessible(name)) { // This caller is not permitted to access this setting. Pretend the setting doesn't @@ -1811,7 +1771,7 @@ public class SettingsProvider extends ContentProvider { enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // If this is a setting that is currently restricted for this user, do not allow // unrestricting changes. @@ -1820,7 +1780,7 @@ public class SettingsProvider extends ContentProvider { } // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name); + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); // Only the owning user can change the setting. if (owningUserId != callingUserId) { @@ -1832,28 +1792,25 @@ public class SettingsProvider extends ContentProvider { // Mutate the value. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS, overrideableByRestore); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS); } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS); } - - case MUTATION_OPERATION_RESET: { - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE, + case MUTATION_OPERATION_RESET -> { + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE, UserHandle.USER_SYSTEM, callingPackage, mode, tag); - } return true; + } } } @@ -1866,7 +1823,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId); synchronized (mLock) { List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId); @@ -1903,7 +1860,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // Ensure the caller can access the setting. enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId()); @@ -1978,7 +1935,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(runAsUserId); if (isSettingRestrictedForUser(name, callingUserId, value, Binder.getCallingUid())) { Slog.e(LOG_TAG, "UserId: " + callingUserId + " is disallowed to change system " @@ -2012,37 +1969,30 @@ public class SettingsProvider extends ContentProvider { // Mutate the value. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { validateSystemSettingValue(name, value); success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null, overrideableByRestore); - break; } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, false, null); - break; } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { validateSystemSettingValue(name, value); success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null); - break; } - - case MUTATION_OPERATION_RESET: { + case MUTATION_OPERATION_RESET -> { success = mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM, runAsUserId, callingPackage, mode, tag); - break; } - - default: + default -> { success = false; Slog.e(LOG_TAG, "Unknown operation code: " + operation); + } } } @@ -2113,8 +2063,8 @@ public class SettingsProvider extends ContentProvider { * Returns {@code true} if the specified secure setting should be accessible to the caller. */ private boolean isSecureSettingAccessible(String name) { - switch (name) { - case "bluetooth_address": + return switch (name) { + case "bluetooth_address" -> // BluetoothManagerService for some reason stores the Android's Bluetooth MAC // address in this secure setting. Secure settings can normally be read by any app, // which thus enables them to bypass the recently introduced restrictions on access @@ -2122,22 +2072,23 @@ public class SettingsProvider extends ContentProvider { // To mitigate this we make this setting available only to callers privileged to see // this device's MAC addresses, same as through public API // BluetoothAdapter.getAddress() (see BluetoothManagerService for details). - return getContext().checkCallingOrSelfPermission( - Manifest.permission.LOCAL_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED; - default: - return true; - } + getContext().checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) + == PackageManager.PERMISSION_GRANTED; + default -> true; + }; } - private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) { - return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting); + private int resolveOwningUserIdForSecureSetting(int userId, String setting) { + // no need to lock because sSecureCloneToManagedSettings is never modified + return resolveOwningUserId(userId, sSecureCloneToManagedSettings, setting); } + @GuardedBy("mLock") private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) { final int parentId; // Resolves dependency if setting has a dependency and the calling user has a parent if (sSystemCloneFromParentOnDependency.containsKey(setting) - && (parentId = getGroupParentLocked(userId)) != userId) { + && (parentId = getGroupParent(userId)) != userId) { // The setting has a dependency and the profile has a parent String dependency = sSystemCloneFromParentOnDependency.get(setting); // Lookup the dependency setting as ourselves, some callers may not have access to it. @@ -2151,11 +2102,11 @@ public class SettingsProvider extends ContentProvider { Binder.restoreCallingIdentity(token); } } - return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting); + return resolveOwningUserId(userId, sSystemCloneToManagedSettings, setting); } - private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) { - final int parentId = getGroupParentLocked(userId); + private int resolveOwningUserId(int userId, Set<String> keys, String name) { + final int parentId = getGroupParent(userId); if (parentId != userId && keys.contains(name)) { return parentId; } @@ -2174,9 +2125,8 @@ public class SettingsProvider extends ContentProvider { } switch (operation) { - case MUTATION_OPERATION_INSERT: - // Insert updates. - case MUTATION_OPERATION_UPDATE: { + // Insert updates. + case MUTATION_OPERATION_INSERT, MUTATION_OPERATION_UPDATE -> { if (Settings.System.PUBLIC_SETTINGS.contains(name)) { return; } @@ -2192,9 +2142,8 @@ public class SettingsProvider extends ContentProvider { warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk( packageInfo.applicationInfo.targetSdkVersion, name); - } break; - - case MUTATION_OPERATION_DELETE: { + } + case MUTATION_OPERATION_DELETE -> { if (Settings.System.PUBLIC_SETTINGS.contains(name) || Settings.System.PRIVATE_SETTINGS.contains(name)) { throw new IllegalArgumentException("You cannot delete system defined" @@ -2212,34 +2161,26 @@ public class SettingsProvider extends ContentProvider { warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk( packageInfo.applicationInfo.targetSdkVersion, name); - } break; + } } } - private Set<String> getInstantAppAccessibleSettings(int settingsType) { - switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: - return Settings.Global.INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SECURE: - return Settings.Secure.INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SYSTEM: - return Settings.System.INSTANT_APP_SETTINGS; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); - } + private static Set<String> getInstantAppAccessibleSettings(int settingsType) { + return switch (settingsType) { + case SETTINGS_TYPE_GLOBAL -> Global.INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SECURE -> Secure.INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SYSTEM -> Settings.System.INSTANT_APP_SETTINGS; + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); + }; } - private Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) { - switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: - return OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SYSTEM: - return OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SECURE: - return OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); - } + private static Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) { + return switch (settingsType) { + case SETTINGS_TYPE_GLOBAL -> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SYSTEM -> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SECURE -> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS; + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); + }; } @GuardedBy("mLock") @@ -2270,7 +2211,7 @@ public class SettingsProvider extends ContentProvider { switch (settingName) { // missing READ_PRIVILEGED_PHONE_STATE permission protection // see alternative API {@link SubscriptionManager#getPreferredDataSubscriptionId() - case Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION: + case Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION -> { // app-compat handling, not break apps targeting on previous SDKs. if (CompatChanges.isChangeEnabled( ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL)) { @@ -2278,7 +2219,7 @@ public class SettingsProvider extends ContentProvider { Manifest.permission.READ_PRIVILEGED_PHONE_STATE, "access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION"); } - break; + } } if (!ai.isInstantApp()) { return; @@ -2306,23 +2247,22 @@ public class SettingsProvider extends ContentProvider { final Set<String> readableFields; final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk; switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: + case SETTINGS_TYPE_GLOBAL -> { allFields = sAllGlobalSettings; readableFields = sReadableGlobalSettings; readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk; - break; - case SETTINGS_TYPE_SYSTEM: + } + case SETTINGS_TYPE_SYSTEM -> { allFields = sAllSystemSettings; readableFields = sReadableSystemSettings; readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk; - break; - case SETTINGS_TYPE_SECURE: + } + case SETTINGS_TYPE_SECURE -> { allFields = sAllSecureSettings; readableFields = sReadableSecureSettings; readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk; - break; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); + } + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); } if (allFields.contains(settingName)) { @@ -2380,7 +2320,7 @@ public class SettingsProvider extends ContentProvider { throw new IllegalStateException("Calling package doesn't exist"); } - private int getGroupParentLocked(int userId) { + private int getGroupParent(int userId) { // Most frequent use case. if (userId == UserHandle.USER_SYSTEM) { return userId; @@ -2480,7 +2420,7 @@ public class SettingsProvider extends ContentProvider { } } - private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) { + private static int resolveCallingUserIdEnforcingPermissions(int requestingUserId) { if (requestingUserId == UserHandle.getCallingUserId()) { return requestingUserId; } @@ -2654,28 +2594,28 @@ public class SettingsProvider extends ContentProvider { private static int getResetModeEnforcingPermission(Bundle args) { final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0; switch (mode) { - case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: { + case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset to untrusted defaults"); } return mode; } - case Settings.RESET_MODE_UNTRUSTED_CHANGES: { + case Settings.RESET_MODE_UNTRUSTED_CHANGES -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset untrusted changes"); } return mode; } - case Settings.RESET_MODE_TRUSTED_DEFAULTS: { + case Settings.RESET_MODE_TRUSTED_DEFAULTS -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset to trusted defaults"); } return mode; } - case Settings.RESET_MODE_PACKAGE_DEFAULTS: { + case Settings.RESET_MODE_PACKAGE_DEFAULTS -> { return mode; } } @@ -2736,21 +2676,18 @@ public class SettingsProvider extends ContentProvider { String column = cursor.getColumnName(i); switch (column) { - case Settings.NameValueTable._ID: { + case Settings.NameValueTable._ID -> { values[i] = setting.getId(); - } break; - - case Settings.NameValueTable.NAME: { + } + case Settings.NameValueTable.NAME -> { values[i] = setting.getName(); - } break; - - case Settings.NameValueTable.VALUE: { + } + case Settings.NameValueTable.VALUE -> { values[i] = setting.getValue(); - } break; - - case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: { + } + case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE -> { values[i] = String.valueOf(setting.isValuePreservedInRestore()); - } break; + } } } @@ -2762,19 +2699,11 @@ public class SettingsProvider extends ContentProvider { } private String resolveCallingPackage() { - switch (Binder.getCallingUid()) { - case Process.ROOT_UID: { - return "root"; - } - - case Process.SHELL_UID: { - return "com.android.shell"; - } - - default: { - return getCallingPackage(); - } - } + return switch (Binder.getCallingUid()) { + case Process.ROOT_UID -> "root"; + case Process.SHELL_UID -> "com.android.shell"; + default -> getCallingPackage(); + }; } private static final class Arguments { @@ -2796,17 +2725,17 @@ public class SettingsProvider extends ContentProvider { public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) { final int segmentSize = uri.getPathSegments().size(); switch (segmentSize) { - case 1: { + case 1 -> { if (where != null && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches() - || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches()) + || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches()) && whereArgs.length == 1) { name = whereArgs[0]; table = computeTableForSetting(uri, name); return; } else if (where != null && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches() - || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) { + || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) { final int startIndex = Math.max(where.indexOf("'"), where.indexOf("\"")) + 1; final int endIndex = Math.max(where.lastIndexOf("'"), @@ -2819,15 +2748,14 @@ public class SettingsProvider extends ContentProvider { table = computeTableForSetting(uri, null); return; } - } break; - - case 2: { + } + case 2 -> { if (where == null && whereArgs == null) { name = uri.getPathSegments().get(1); table = computeTableForSetting(uri, name); return; } - } break; + } } EventLogTags.writeUnsupportedSettingsQuery( @@ -2960,6 +2888,7 @@ public class SettingsProvider extends ContentProvider { mBackupManager = new BackupManager(getContext()); } + @GuardedBy("mLock") private void generateUserKeyLocked(int userId) { // Generate a random key for each user used for creating a new ssaid. final byte[] keyBytes = new byte[32]; @@ -2983,6 +2912,7 @@ public class SettingsProvider extends ContentProvider { return ByteBuffer.allocate(4).putInt(data.length).array(); } + @GuardedBy("mLock") public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) { // Read the user's key from the ssaid table. Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY); @@ -3044,6 +2974,7 @@ public class SettingsProvider extends ContentProvider { return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid); } + @GuardedBy("mLock") private void syncSsaidTableOnStartLocked() { // Verify that each user's packages and ssaid's are in sync. for (UserInfo user : mUserManager.getAliveUsers()) { @@ -3078,15 +3009,17 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public List<String> getSettingsNamesLocked(int type, int userId) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return new ArrayList<>(); } return settingsState.getSettingNamesLocked(); } + @GuardedBy("mLock") public SparseBooleanArray getKnownUsersLocked() { SparseBooleanArray users = new SparseBooleanArray(); for (int i = mSettingsStates.size()-1; i >= 0; i--) { @@ -3095,17 +3028,19 @@ public class SettingsProvider extends ContentProvider { return users; } + @GuardedBy("mLock") @Nullable public SettingsState getSettingsLocked(int type, int userId) { final int key = makeKey(type, userId); - return peekSettingsStateLocked(key); + return mSettingsStates.get(key); } - public boolean ensureSettingsForUserLocked(int userId) { + @GuardedBy("mLock") + public void ensureSettingsForUserLocked(int userId) { // First make sure this user actually exists. if (mUserManager.getUserInfo(userId) == null) { Slog.wtf(LOG_TAG, "Requested user " + userId + " does not exist"); - return false; + return; } // Migrate the setting for this user if needed. @@ -3143,9 +3078,9 @@ public class SettingsProvider extends ContentProvider { // Upgrade the settings to the latest version. UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); - return true; } + @GuardedBy("mLock") private void ensureSettingsStateLocked(int key) { if (mSettingsStates.get(key) == null) { final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key)); @@ -3155,6 +3090,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public void removeUserStateLocked(int userId, boolean permanently) { // We always keep the global settings in memory. @@ -3166,12 +3102,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(systemKey); systemSettingsState.destroyLocked(null); } else { - systemSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(systemKey); - } - }); + systemSettingsState.destroyLocked(() -> mSettingsStates.remove(systemKey)); } } @@ -3183,12 +3114,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(secureKey); secureSettingsState.destroyLocked(null); } else { - secureSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(secureKey); - } - }); + secureSettingsState.destroyLocked(() -> mSettingsStates.remove(secureKey)); } } @@ -3200,12 +3126,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(ssaidKey); ssaidSettingsState.destroyLocked(null); } else { - ssaidSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(ssaidKey); - } - }); + ssaidSettingsState.destroyLocked(() -> mSettingsStates.remove(ssaidKey)); } } @@ -3213,6 +3134,7 @@ public class SettingsProvider extends ContentProvider { mGenerationRegistry.onUserRemoved(userId); } + @GuardedBy("mLock") public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) { @@ -3220,6 +3142,7 @@ public class SettingsProvider extends ContentProvider { packageName, forceNotify, criticalSettings, overrideableByRestore); } + @GuardedBy("mLock") public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName, boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) { @@ -3232,7 +3155,7 @@ public class SettingsProvider extends ContentProvider { boolean success = false; boolean wasUnsetNonPredefinedSetting = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) { wasUnsetNonPredefinedSetting = true; @@ -3264,9 +3187,10 @@ public class SettingsProvider extends ContentProvider { * Set Config Settings using consumed keyValues, returns true if the keyValues can be set, * false otherwise. */ + @GuardedBy("mLock") public boolean setConfigSettingsLocked(int key, String prefix, Map<String, String> keyValues, String packageName) { - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) { return false; @@ -3283,12 +3207,13 @@ public class SettingsProvider extends ContentProvider { return true; } + @GuardedBy("mLock") public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify, Set<String> criticalSettings) { final int key = makeKey(type, userId); boolean success = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { success = settingsState.deleteSettingLocked(name); } @@ -3306,13 +3231,14 @@ public class SettingsProvider extends ContentProvider { return success; } + @GuardedBy("mLock") public boolean updateSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, Set<String> criticalSettings) { final int key = makeKey(type, userId); boolean success = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { success = settingsState.updateSettingLocked(name, value, tag, makeDefault, packageName); @@ -3331,10 +3257,11 @@ public class SettingsProvider extends ContentProvider { return success; } + @GuardedBy("mLock") public Setting getSettingLocked(int type, int userId, String name) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return null; } @@ -3352,16 +3279,18 @@ public class SettingsProvider extends ContentProvider { return Global.SECURE_FRP_MODE.equals(setting.getName()); } + @GuardedBy("mLock") public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ null); } + @GuardedBy("mLock") public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag, @Nullable String prefix) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return false; } @@ -3369,7 +3298,7 @@ public class SettingsProvider extends ContentProvider { boolean success = false; banConfigurationIfNecessary(type, prefix, settingsState); switch (mode) { - case Settings.RESET_MODE_PACKAGE_DEFAULTS: { + case Settings.RESET_MODE_PACKAGE_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3389,9 +3318,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: { + } + case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3411,9 +3339,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_UNTRUSTED_CHANGES: { + } + case Settings.RESET_MODE_UNTRUSTED_CHANGES -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3439,9 +3366,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_TRUSTED_DEFAULTS: { + } + case Settings.RESET_MODE_TRUSTED_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; @@ -3464,11 +3390,12 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; + } } return success; } + @GuardedBy("mLock") public void removeSettingsForPackageLocked(String packageName, int userId) { // Global and secure settings are signature protected. Apps signed // by the platform certificate are generally not uninstalled and @@ -3482,6 +3409,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public void onUidRemovedLocked(int uid) { final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, UserHandle.getUserId(uid)); @@ -3490,19 +3418,7 @@ public class SettingsProvider extends ContentProvider { } } - @Nullable - private SettingsState peekSettingsStateLocked(int key) { - SettingsState settingsState = mSettingsStates.get(key); - if (settingsState != null) { - return settingsState; - } - - if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) { - return null; - } - return mSettingsStates.get(key); - } - + @GuardedBy("mLock") private void migrateAllLegacySettingsIfNeededLocked() { final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); File globalFile = getSettingsFile(key); @@ -3538,6 +3454,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void migrateLegacySettingsForUserIfNeededLocked(int userId) { // Every user has secure settings and if no file we need to migrate. final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId); @@ -3552,6 +3469,7 @@ public class SettingsProvider extends ContentProvider { migrateLegacySettingsForUserLocked(dbHelper, database, userId); } + @GuardedBy("mLock") private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) { // Move over the system settings. @@ -3596,6 +3514,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void migrateLegacySettingsLocked(SettingsState settingsState, SQLiteDatabase database, String table) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); @@ -3630,7 +3549,7 @@ public class SettingsProvider extends ContentProvider { } } - @GuardedBy("secureSettings.mLock") + @GuardedBy("mLock") private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) { Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID); @@ -3706,6 +3625,7 @@ public class SettingsProvider extends ContentProvider { name, type, changeType); } + @GuardedBy("mLock") private void notifyForConfigSettingsChangeLocked(int key, String prefix, List<String> changedSettings) { @@ -3787,30 +3707,18 @@ public class SettingsProvider extends ContentProvider { } } - private File getSettingsFile(int key) { - if (isConfigSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_CONFIG); - } else if (isGlobalSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_GLOBAL); - } else if (isSystemSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SYSTEM); - } else if (isSecureSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SECURE); - } else if (isSsaidSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SSAID); - } else { - throw new IllegalArgumentException("Invalid settings key:" + key); - } + private static File getSettingsFile(int key) { + final int userId = getUserIdFromKey(key); + final int type = getTypeFromKey(key); + final File userSystemDirectory = Environment.getUserSystemDirectory(userId); + return switch (type) { + case SETTINGS_TYPE_CONFIG -> new File(userSystemDirectory, SETTINGS_FILE_CONFIG); + case SETTINGS_TYPE_GLOBAL -> new File(userSystemDirectory, SETTINGS_FILE_GLOBAL); + case SETTINGS_TYPE_SYSTEM -> new File(userSystemDirectory, SETTINGS_FILE_SYSTEM); + case SETTINGS_TYPE_SECURE -> new File(userSystemDirectory, SETTINGS_FILE_SECURE); + case SETTINGS_TYPE_SSAID -> new File(userSystemDirectory, SETTINGS_FILE_SSAID); + default -> throw new IllegalArgumentException("Invalid settings key:" + key); + }; } private Uri getNotificationUriFor(int key, String name) { @@ -3833,14 +3741,11 @@ public class SettingsProvider extends ContentProvider { private int getMaxBytesPerPackageForType(int type) { switch (type) { - case SETTINGS_TYPE_CONFIG: - case SETTINGS_TYPE_GLOBAL: - case SETTINGS_TYPE_SECURE: - case SETTINGS_TYPE_SSAID: { + case SETTINGS_TYPE_CONFIG, SETTINGS_TYPE_GLOBAL, SETTINGS_TYPE_SECURE, + SETTINGS_TYPE_SSAID -> { return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED; } - - default: { + default -> { return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED; } } @@ -3857,7 +3762,7 @@ public class SettingsProvider extends ContentProvider { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_NOTIFY_URI_CHANGED: { + case MSG_NOTIFY_URI_CHANGED -> { final int userId = msg.arg1; Uri uri = (Uri) msg.obj; try { @@ -3868,12 +3773,11 @@ public class SettingsProvider extends ContentProvider { if (DEBUG) { Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri); } - } break; - - case MSG_NOTIFY_DATA_CHANGED: { + } + case MSG_NOTIFY_DATA_CHANGED -> { mBackupManager.dataChanged(); scheduleWriteFallbackFilesJob(); - } break; + } } } } @@ -3887,6 +3791,7 @@ public class SettingsProvider extends ContentProvider { mUserId = userId; } + @GuardedBy("mLock") public void upgradeIfNeededLocked() { // The version of all settings for a user is the same (all users have secure). SettingsState secureSettings = getSettingsLocked( @@ -3944,18 +3849,22 @@ public class SettingsProvider extends ContentProvider { systemSettings.setVersionLocked(newVersion); } + @GuardedBy("mLock") private SettingsState getGlobalSettingsLocked() { return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); } + @GuardedBy("mLock") private SettingsState getSecureSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SECURE, userId); } + @GuardedBy("mLock") private SettingsState getSsaidSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SSAID, userId); } + @GuardedBy("mLock") private SettingsState getSystemSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId); } @@ -5399,7 +5308,7 @@ public class SettingsProvider extends ContentProvider { // next version step. // If this is a new profile, check if a secure setting exists for the // owner of the profile and use that value for the work profile. - int owningId = resolveOwningUserIdForSecureSettingLocked(userId, + int owningId = resolveOwningUserIdForSecureSetting(userId, NOTIFICATION_BUBBLES); Setting previous = getGlobalSettingsLocked() .getSettingLocked("notification_bubbles"); @@ -6068,18 +5977,22 @@ public class SettingsProvider extends ContentProvider { return currentVersion; } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, boolean val) { initGlobalSettingsDefaultValLocked(key, val ? "1" : "0"); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, int val) { initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, long val) { initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, String val) { final SettingsState globalSettings = getGlobalSettingsLocked(); Setting currentSetting = globalSettings.getSettingLocked(key); @@ -6198,6 +6111,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void ensureLegacyDefaultValueAndSystemSetUpdatedLocked(SettingsState settings, int userId) { List<String> names = settings.getSettingNamesLocked(); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 7bca944033d9..9dacadef38d5 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -904,6 +904,7 @@ public class SettingsBackupTest { Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE, Settings.Secure.GAME_DASHBOARD_ALWAYS_ON, Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, + Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT, Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, Settings.Secure.LOCATION_COARSE_ACCURACY_M, Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java index 2b33057145d3..2984cd3db1c3 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java @@ -17,10 +17,12 @@ package com.android.providers.settings; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertNotEquals; import static org.junit.Assume.assumeTrue; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.UserInfo; import android.os.Process; @@ -42,6 +44,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; @LargeTest public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTest { @@ -54,35 +58,42 @@ public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTes private boolean mSystemSetUserRestriction; private List<Integer> mUsersAddedByTest; - private String waitTillValueChanges(String errorMessage, String oldValue) { + private static String waitTillValueChanges(String errorMessage, @Nullable String oldValue, + Supplier<String> action) { boolean interrupted = false; final long startTime = SystemClock.uptimeMillis(); - String newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS); - while (newValue.equals(oldValue) && SystemClock.uptimeMillis() <= (startTime + String newValue = action.get(); + while (Objects.equals(newValue, oldValue) && SystemClock.uptimeMillis() <= (startTime + USER_RESTRICTION_CHANGE_TIMEOUT)) { try { Thread.sleep(1000); } catch (InterruptedException exc) { interrupted = true; } - newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS); + newValue = action.get(); } if (interrupted) { Thread.currentThread().interrupt(); } - assertFalse(errorMessage, oldValue.equals(newValue)); + assertNotEquals(errorMessage, newValue, oldValue); return newValue; } - private String getSecureSettingForUserViaShell(int userId) throws IOException { + private String getSecureSettingForUserViaShell(int userId) { StringBuilder sb = new StringBuilder("settings get --user "); sb.append(userId + " secure "); sb.append(Settings.Secure.INSTALL_NON_MARKET_APPS); BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream( InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( sb.toString()).getFileDescriptor()))); - String line = reader.readLine(); - return line.trim(); + String line; + try { + line = reader.readLine(); + } catch (IOException e) { + return null; + } + final String result = line.trim(); + return (result.isEmpty() || result.equals("null")) ? null : result; } @Override @@ -121,7 +132,10 @@ public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTes UserInfo newUser = mUm.createUser("TEST_USER", 0); mUsersAddedByTest.add(newUser.id); - String value = getSecureSettingForUserViaShell(newUser.id); + // wait till SettingsProvider reacts to the USER_ADDED event + String value = waitTillValueChanges( + "install_non_market_apps should be set for the new user", null, + () -> getSecureSettingForUserViaShell(newUser.id)); assertEquals("install_non_market_apps should be 1 for a new user", value, "1"); } @@ -140,13 +154,15 @@ public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTes mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, !mHasUserRestriction); value = waitTillValueChanges( "Changing user restriction did not change the value of install_non_market_apps", - value); + value, + () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS)); assertTrue("Invalid value", value.equals("1") || value.equals("0")); mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, mHasUserRestriction); value = waitTillValueChanges( "Changing user restriction did not change the value of install_non_market_apps", - value); + value, + () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS)); assertTrue("Invalid value", value.equals("1") || value.equals("0")); } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 11ae9c35898b..10d04d3ff6b3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -860,6 +860,10 @@ <!-- Permission required for CTS test - CtsWallpaperTestCases --> <uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" /> + <!-- Permissions required for CTS test - CtsVoiceInteractionTestCases --> + <uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" /> + <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e6a82e83433b..17cc9f8135f4 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -485,6 +485,8 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", + "flag-junit", + "platform-test-annotations", ], } diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 8f329b3dddd8..34545cf190f3 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -36,6 +36,7 @@ gwasserman@google.com hwwang@google.com hyunyoungs@google.com ikateryna@google.com +iyz@google.com jaggies@google.com jamesoleary@google.com jbolinger@google.com @@ -54,10 +55,12 @@ justinweir@google.com kozynski@google.com kprevas@google.com lusilva@google.com +liuyining@google.com lynhan@google.com madym@google.com mankoff@google.com mateuszc@google.com +matiashe@google.com mgalhardo@google.com michaelmikhil@google.com michschn@google.com @@ -94,6 +97,7 @@ tracyzhou@google.com tsuji@google.com twickham@google.com vadimt@google.com +valiiftime@google.com vanjan@google.com victortulias@google.com winsonc@google.com diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index bcf1535b94fa..08ecf09b2365 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -8,3 +8,10 @@ flag { description: "Adjusts bounds to allow the floating menu to render on top of navigation bars." bug: "283768342" } + +flag { + name: "floating_menu_ime_displacement_animation" + namespace: "accessibility" + description: "Adds an animation for when the FAB is displaced by an IME becoming visible." + bug: "281150010" +}
\ No newline at end of file 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 4ea57a8cc007..ab4db451406d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -290,9 +290,10 @@ class ActivityLaunchAnimator( controller: Controller?, animate: Boolean = true, packageName: String? = null, + showOverLockscreen: Boolean = false, intentStarter: PendingIntentStarter ) { - startIntentWithAnimation(controller, animate, packageName) { + startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) { intentStarter.startPendingIntent(it) } } 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 index df87d19db5a6..91fe33cd2f4b 100644 --- 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 @@ -30,6 +30,8 @@ class CommunalLayoutEngine { * * Currently treats the first supported size as the size to be rendered in, ignoring other * supported sizes. + * + * Cards are ordered by priority, from highest to lowest. */ fun distributeCardsIntoColumns( cards: List<CommunalGridLayoutCard>, @@ -37,7 +39,8 @@ class CommunalLayoutEngine { val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() var capacityOfLastColumn = 0 - for (card in cards) { + val sorted = cards.sortedByDescending { it.priority } + for (card in sorted) { val cardSize = card.supportedSizes.first() if (capacityOfLastColumn >= cardSize.value) { // Card fits in last column 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 index c1974caa5628..50b7c5f02068 100644 --- 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 @@ -41,7 +41,7 @@ class CommunalLayoutEngineTest { ), ) - assertDistribution(cards, expected) + assertDistributionBySize(cards, expected) } @Test @@ -71,10 +71,30 @@ class CommunalLayoutEngineTest { ), ) - assertDistribution(cards, expected) + assertDistributionBySize(cards, expected) } - private fun assertDistribution( + @Test + fun distribution_sortByPriority() { + val cards = + listOf( + generateCard(priority = 2), + generateCard(priority = 7), + generateCard(priority = 10), + generateCard(priority = 1), + generateCard(priority = 5), + ) + val expected = + listOf( + listOf(10, 7), + listOf(5, 2), + listOf(1), + ) + + assertDistributionByPriority(cards, expected) + } + + private fun assertDistributionBySize( cards: List<CommunalGridLayoutCard>, expected: List<List<CommunalGridLayoutCard.Size>>, ) { @@ -87,6 +107,19 @@ class CommunalLayoutEngineTest { } } + private fun assertDistributionByPriority( + cards: List<CommunalGridLayoutCard>, + expected: List<List<Int>>, + ) { + val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) + + for (c in expected.indices) { + for (r in expected[c].indices) { + assertThat(result[c][r].card.priority).isEqualTo(expected[c][r]) + } + } + } + private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { return object : CommunalGridLayoutCard() { override val supportedSizes = listOf(size) @@ -97,4 +130,16 @@ class CommunalLayoutEngineTest { } } } + + private fun generateCard(priority: Int): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(Size.HALF) + override val priority = priority + + @Composable + override fun Content(modifier: Modifier, size: SizeF) { + Card(modifier = modifier, content = {}) + } + } + } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index 17c74ba7b12f..0e7694e7ef46 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -315,7 +315,8 @@ internal class ExpandableControllerImpl( } } - override fun shouldAnimateExit(): Boolean = isComposed.value + override fun shouldAnimateExit(): Boolean = + isComposed.value && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown override fun onExitAnimationCancelled() { isDialogShowing.value = false diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 689a0a2815c5..eb71490f049a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -46,7 +46,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.layout @@ -69,7 +68,6 @@ import com.android.compose.animation.Expandable import com.android.compose.modifiers.background import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.theme.colorAttr -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon @@ -78,6 +76,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.res.R import kotlinx.coroutines.launch /** The Quick Settings footer actions row. */ @@ -89,8 +88,7 @@ fun FooterActions( ) { val context = LocalContext.current - // Collect visibility and alphas as soon as we are composed, even when not visible. - val isVisible by viewModel.isVisible.collectAsState() + // Collect alphas as soon as we are composed, even when not visible. val alpha by viewModel.alpha.collectAsState() val backgroundAlpha = viewModel.backgroundAlpha.collectAsState() @@ -142,11 +140,6 @@ fun FooterActions( modifier .fillMaxWidth() .graphicsLayer { this.alpha = alpha } - .drawWithContent { - if (isVisible) { - drawContent() - } - } .then(backgroundModifier) .padding( top = dimensionResource(R.dimen.qs_footer_actions_top_padding), @@ -328,7 +321,7 @@ private fun TextButton( shape = CircleShape, color = colorAttr(R.attr.underSurface), contentColor = LocalAndroidColorScheme.current.onSurfaceVariant, - borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)), + borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)), modifier = modifier.padding(horizontal = 4.dp), onClick = onClick, ) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index d70a2480468a..2dc53ab8bf76 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -157,6 +157,8 @@ class SceneGestureHandler( */ private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() } + internal var gestureWithPriority: Any? = null + internal fun onDragStarted() { if (isDrivingTransition) { // This [transition] was already driving the animation: simply take over it. @@ -525,15 +527,21 @@ private class SceneDraggableHandler( private val gestureHandler: SceneGestureHandler, ) : DraggableHandler { override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) { + gestureHandler.gestureWithPriority = this gestureHandler.onDragStarted() } override fun onDelta(pixels: Float) { - gestureHandler.onDrag(delta = pixels) + if (gestureHandler.gestureWithPriority == this) { + gestureHandler.onDrag(delta = pixels) + } } override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) { - gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true) + if (gestureHandler.gestureWithPriority == this) { + gestureHandler.gestureWithPriority = null + gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true) + } } } @@ -615,10 +623,15 @@ class SceneNestedScrollHandler( }, canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key }, onStart = { + gestureHandler.gestureWithPriority = this priorityScene = nextScene gestureHandler.onDragStarted() }, onScroll = { offsetAvailable -> + if (gestureHandler.gestureWithPriority != this) { + return@PriorityNestedScrollConnection Offset.Zero + } + val amount = offsetAvailable.toAmount() // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is @@ -628,6 +641,10 @@ class SceneNestedScrollHandler( amount.toOffset() }, onStop = { velocityAvailable -> + if (gestureHandler.gestureWithPriority != this) { + return@PriorityNestedScrollConnection Velocity.Zero + } + priorityScene = null gestureHandler.onDragStopped( diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 9b9e70dbefe4..6791a85ff21c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -312,4 +312,52 @@ class SceneGestureHandlerTest { advanceUntilIdle() assertScene(currentScene = SceneA, isIdle = true) } + + @Test + fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest { + draggable.onDelta(deltaInPixels10) + assertScene(currentScene = SceneA, isIdle = true) + } + @Test + fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest { + draggable.onDragStopped(coroutineScope, velocityThreshold) + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest { + nestedScroll.onPreFling(Velocity(0f, velocityThreshold)) + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun startNestedScrollWhileDragging() = runGestureTest { + draggable.onDragStarted(coroutineScope, Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + val transition = transitionState as Transition + + draggable.onDelta(deltaInPixels10) + assertThat(transition.progress).isEqualTo(0.1f) + + // now we can intercept the scroll events + nestedScrollEvents(available = offsetY10) + assertThat(transition.progress).isEqualTo(0.2f) + + // this should be ignored, we are scrolling now! + draggable.onDragStopped(coroutineScope, velocityThreshold) + assertScene(currentScene = SceneA, isIdle = false) + + nestedScrollEvents(available = offsetY10) + assertThat(transition.progress).isEqualTo(0.3f) + + nestedScrollEvents(available = offsetY10) + assertThat(transition.progress).isEqualTo(0.4f) + + nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) + assertScene(currentScene = SceneC, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneC, isIdle = true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 07ffd112300b..162008808791 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -1,15 +1,17 @@ /* * 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 + * 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. + * 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; @@ -19,8 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -29,9 +29,9 @@ import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -43,12 +43,13 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.concurrent.atomic.AtomicBoolean; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class SystemUIDialogTest extends SysuiTestCase { @@ -76,12 +77,13 @@ public class SystemUIDialogTest extends SysuiTestCase { dialog.show(); verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(), - intentFilterCaptor.capture(), eq(null), any()); + intentFilterCaptor.capture(), ArgumentMatchers.eq(null), ArgumentMatchers.any()); assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF)); assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); dialog.dismiss(); - verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue())); + verify(mBroadcastDispatcher).unregisterReceiver( + ArgumentMatchers.eq(broadcastReceiverCaptor.getValue())); } @@ -90,11 +92,12 @@ public class SystemUIDialogTest extends SysuiTestCase { final SystemUIDialog dialog = new SystemUIDialog(mContext, 0, false); dialog.show(); - verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), eq(null), any()); + verify(mBroadcastDispatcher, never()).registerReceiver(ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.eq(null), ArgumentMatchers.any()); assertTrue(dialog.isShowing()); dialog.dismiss(); - verify(mBroadcastDispatcher, never()).unregisterReceiver(any()); + verify(mBroadcastDispatcher, never()).unregisterReceiver(ArgumentMatchers.any()); assertFalse(dialog.isShowing()); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 9cc87fde122f..f0e3c99b007d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -57,6 +57,16 @@ public interface ActivityStarter { @Nullable ActivityLaunchAnimator.Controller animationController); /** + * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching + * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it + * will show over keyguard without first dimissing it. If it doesn't support it, calling this + * method is exactly the same as calling {@link #startPendingIntentDismissingKeyguard}. + */ + void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, + @Nullable Runnable intentSentUiThreadCallback, + @Nullable ActivityLaunchAnimator.Controller animationController); + + /** * The intent flag can be specified in startActivity(). */ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml index 2eb1bb5dd941..a6a81222a8cb 100644 --- a/packages/SystemUI/res-keyguard/values-af/strings.xml +++ b/packages/SystemUI/res-keyguard/values-af/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laai tans stadig"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laaiproses word geoptimeer om battery te beskerm"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kwessie met laaibykomstigheid"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Druk Kieslys om te ontsluit."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netwerk is gesluit"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Geen SIM nie"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Voeg ’n SIM by."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Die SIM is weg of nie leesbaar nie. Voeg ’n SIM by."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jou SIM is permanent gedeaktiveer.\n Kontak jou draadlose diensverskaffer vir ’n ander SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is gesluit."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-gesluit."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ontsluit tans SIM …"</string> diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml index 5fd946b1f188..fb84414c19df 100644 --- a/packages/SystemUI/res-keyguard/values-am/strings.xml +++ b/packages/SystemUI/res-keyguard/values-am/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ባትሪን ለመጠበቅ ኃይል መሙላት ተብቷል"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ተለዋዋጭን ኃይል በመሙላት ላይ ችግር"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ለመክፈት ምናሌ ተጫን።"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"አውታረ መረብ ተቆልፏል"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ምንም SIM የለም"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ሲም ያክሉ።"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ሲሙ ጠፍቷል ወይም አይነበብም። ሲም ያክሉ።"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ጥቅም ላይ የማይውል ሲም።"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ሲምዎ በቋሚነት ቦዝኗል።\n ለሌላ ሲም የእርስዎን አገልግሎት ሰጪ ያግኙ።"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ሲም ተቆልፏል።"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ሲም በPUK የተቆለፈ ነው።"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ሲምን በመክፈት ላይ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index b6479f456a2f..fb330929f4d3 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن ببطء"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • تم تحسين الشحن لحماية البطارية"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • مشكلة متعلّقة بجهاز الشحن الملحق"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"اضغط على \"القائمة\" لإلغاء التأمين."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"الشبكة مؤمّنة"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"لا تتوفر شريحة SIM."</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"يجب إضافة شريحة SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"شريحة SIM مفقودة أو غير قابلة للقراءة. يجب إضافة شريحة SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"شريحة SIM غير قابلة للاستخدام."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"تم إيقاف شريحة SIM نهائيًا.\n عليك التواصل مع مقدم خدمة اللاسلكي للحصول على شريحة SIM أخرى."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"شريحة SIM مُقفَلة."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"شريحة SIM مُقفَلة برمز PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"جارٍ إلغاء قفل شريحة SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml index a41a704f345a..a123bb795708 100644 --- a/packages/SystemUI/res-keyguard/values-as/strings.xml +++ b/packages/SystemUI/res-keyguard/values-as/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • লাহে লাহে চাৰ্জ কৰি থকা হৈছে"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং অপ্টিমাইজ কৰা হৈছে"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চাৰ্জিঙৰ আনুষংগিক সামগ্ৰীত সমস্যা হৈছে"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"আনলক কৰিবলৈ মেনু টিপক।"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটৱর্ক লক কৰা অৱস্থাত আছে"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনো ছিম নাই"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"এখন ছিম যোগ দিয়ক।"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ছিম নাই অথবা সেইখন পঢ়িব নোৱাৰি। এখন ছিম যোগ দিয়ক।"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যৱহাৰ কৰিব নোৱৰা ছিম।"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"আপোনাৰ ছিমখন স্থায়ীভাৱে নিষ্ক্ৰিয় কৰা হৈছে।\n অন্য এখন ছিমৰ বাবে আপোনাৰ ৱায়াৰলেছ সেৱা প্ৰদানকাৰীৰ সৈতে যোগাযোগ কৰক।"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ছিমখন লক হৈ আছে।"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ছিমখন PUKৰ দ্বাৰা লক হৈ আছে।"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ছিম আনলক কৰি থকা হৈছে…"</string> diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml index ed969c7c8e29..b133b30a657c 100644 --- a/packages/SystemUI/res-keyguard/values-az/strings.xml +++ b/packages/SystemUI/res-keyguard/values-az/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Yavaş enerji yığır"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyanı qorumaq üçün şarj optimallaşdırılıb"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarı ilə bağlı problem"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Kilidi açmaq üçün Menyu düyməsinə basın."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Şəbəkə kilidlidir"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM yoxdur"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM əlavə edin."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM kart yoxdur və ya oxuna bilinmir. SIM əlavə edin."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"İstifadəyə yararsız SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM kartınız həmişəlik deaktiv edilib.\n Başqa SIM kart üçün simsiz xidmət provayderinizə müraciət edin."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kilidlənib."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM kart PUK ilə kilidlənib."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM kiliddən çıxarılır…"</string> diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml index b0a647199416..9a919624c4fa 100644 --- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sporo se puni"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je optimizovano da bi se zaštitila baterija"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem sa dodatnim priborom za punjenje"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite Meni da biste otključali."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili ne može da se pročita. Dodajte SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM je trajno deaktiviran.\n Obratite se dobavljaču usluge bežične telefonije da biste dobili drugi SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključava se SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml index 11cc77dca5da..5e46b715c0e3 100644 --- a/packages/SystemUI/res-keyguard/values-be/strings.xml +++ b/packages/SystemUI/res-keyguard/values-be/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ідзе павольная зарадка"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • У мэтах зберажэння акумулятара зарадка аптымізавана"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Праблема з зараднай прыладай"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Націсніце кнопку \"Меню\", каб разблакіраваць."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сетка заблакіравана"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM-карты"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Дадайце SIM-карту."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта адсутнічае ці не чытаецца. Дадайце SIM-карту."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непрыдатная для выкарыстання SIM-карта."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ваша SIM-карта адключана назаўсёды.\n Звяжыцеся з аператарам бесправадной сувязі, каб атрымаць іншую SIM-карту."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карта заблакіравана."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карта заблакіравана PUK-кодам."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Разблакіраванне SIM-карты…"</string> diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml index c554a274f466..ab931ede75b0 100644 --- a/packages/SystemUI/res-keyguard/values-bg/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарежда се бавно"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зареждането е оптимизирано с цел запазване на батерията"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем със зарядното устройство"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Натиснете „Меню“, за да отключите."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заключена"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM карта"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Добавете SIM карта."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM картата липсва или е нечетлива. Добавете SIM карта."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неизползваема SIM карта."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картата ви е деактивирана за постоянно.\nЗа да получите друга, се свържете с доставчика си на безжична услуга."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM картата е заключена."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM картата е заключена с PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картата се отключва…"</string> diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 67b4e4bc322b..e25de9318f5d 100644 --- a/packages/SystemUI/res-keyguard/values-bn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ধীরে চার্জ হচ্ছে"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ব্যাটারি ভাল রাখতে চার্জিং অপ্টিমাইজ করা হয়েছে"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চার্জিং অ্যাক্সেসরিতে সমস্যা রয়েছে"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"আনলক করতে মেনুতে টিপুন।"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটওয়ার্ক লক করা আছে"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনও সিম নেই"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"সিম যোগ করুন।"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"সিম নেই অথবা সেটি রিড করা যাচ্ছে না। সিম যোগ করুন।"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যবহারযোগ্য নয় এমন সিম।"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"আপনার সিম স্থায়ীভাবে বন্ধ করে দেওয়া হয়েছে।\n অন্য একটি সিমের জন্য আপনার ওয়্যারলেস পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"সিম লক করা হয়েছে।"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"সিম PUK লক করা হয়েছে।"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"সিম আনলক করা হচ্ছে…"</string> diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml index 4c519c8d5362..cd7aaeb8117a 100644 --- a/packages/SystemUI/res-keyguard/values-bs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sporo punjenje"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je optimizirano radi zaštite baterije"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem s opremom za punjenje"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite meni da otključate."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili se ne može čitati. Dodajte SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM je trajno deaktiviran.\n Kontaktirajte pružaoca bežičnih usluga za drugi SIM"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključavanje SIM-a…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml index 3bd65083d810..bf8a592c27b9 100644 --- a/packages/SystemUI/res-keyguard/values-ca/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant lentament"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Càrrega optimitzada per protegir la bateria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema relacionat amb l\'accessori de càrrega"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Prem Menú per desbloquejar."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"La xarxa està bloquejada"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hi ha cap SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Afegeix una SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la SIM o no es pot llegir. Afegeix una SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La SIM no es pot utilitzar."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"La SIM s\'ha desactivat permanentment.\n Contacta amb el proveïdor de serveis sense fil per obtenir-ne una altra."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM està bloquejada."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM està bloquejada pel PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"S\'està desbloquejant la targeta SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml index 573638bcc135..bedafd8dd3b8 100644 --- a/packages/SystemUI/res-keyguard/values-cs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pomalé nabíjení"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimalizované nabíjení za účelem ochrany baterie"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problém s nabíjecím příslušenstvím"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Klávesy odemknete stisknutím tlačítka nabídky."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Síť je blokována"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Žádná SIM karta"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Přidejte SIM kartu."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta chybí nebo je nečitelná. Přidejte SIM kartu."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM kartu nelze použít."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM karta byla natrvalo deaktivována.\n Požádejte svého poskytovatele bezdrátových služeb o další SIM kartu."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta je zablokována."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta je blokována pomocí kódu PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odblokování SIM karty…"</string> diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml index c7c863b70336..93f505e8bbf2 100644 --- a/packages/SystemUI/res-keyguard/values-da/strings.xml +++ b/packages/SystemUI/res-keyguard/values-da/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader langsomt"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladning er optimeret for at beskytte batteriet"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem med opladertilbehør"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tryk på menuen for at låse op."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netværket er låst"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Intet SIM-kort"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Tilføj et SIM-kort."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kortet mangler eller kan ikke læses. Tilføj et SIM-kort."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Deaktiveret SIM-kort."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Dit SIM-kort er permanent deaktiveret.\n Kontakt din tjenesteudbyder for at få et nyt SIM-kort."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet er låst."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet er låst med PUK-koden."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortet låses op…"</string> diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml index 5c5f264fe508..01e166e36559 100644 --- a/packages/SystemUI/res-keyguard/values-de/strings.xml +++ b/packages/SystemUI/res-keyguard/values-de/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wird langsam geladen"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimiertes Laden zur Akkuschonung"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem mit dem Ladezubehör"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Zum Entsperren die Menütaste drücken."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netzwerk gesperrt"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Keine SIM-Karte"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Lege eine SIM-Karte ein."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-Karte fehlt oder ist nicht lesbar. Lege eine SIM-Karte ein."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-Karte ist nicht nutzbar."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Deine SIM-Karte wurde dauerhaft deaktiviert.\n Wende dich an deinen Mobilfunkanbieter, um eine andere SIM-Karte zu erhalten."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-Karte ist gesperrt."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-Karte ist mit einem PUK gesperrt."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-Karte wird entsperrt…"</string> diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml index 3a01da53dfce..97692424cf2c 100644 --- a/packages/SystemUI/res-keyguard/values-el/strings.xml +++ b/packages/SystemUI/res-keyguard/values-el/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Αργή φόρτιση"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Η φόρτιση βελτιστοποιήθηκε για την προστασία της μπαταρίας"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Πρόβλημα αξεσουάρ φόρτισης"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Πατήστε \"Μενού\" για ξεκλείδωμα."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Κλειδωμένο δίκτυο"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Δεν υπάρχει SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Προσθέστε μια SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Η SIM λείπει ή δεν είναι δυνατή η ανάγνωσή της. Προσθέστε μια SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Η SIM δεν μπορεί να χρησιμοποιηθεί."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Η SIM απενεργοποιήθηκε οριστικά.\n Επικοινωνήστε με τον πάροχο υπηρεσιών ασύρματου δικτύου για μια νέα SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Η SIM είναι κλειδωμένη."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Η SIM έχει κλειδωθεί με κωδικό PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ξεκλείδωμα SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml index a4b13487b3e1..087ab3a79358 100644 --- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml index 480bcbb61954..7297cf929e2f 100644 --- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimized to protect battery"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml index a4b13487b3e1..087ab3a79358 100644 --- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml index a4b13487b3e1..087ab3a79358 100644 --- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml index b8e89f4e334f..ead8bce626b8 100644 --- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimized to protect battery"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Press Menu to unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Add a SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"The SIM is missing or not readable. Add a SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM is locked."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM is PUK-locked."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Unlocking SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml index debbeb127c2f..5b82c443ff62 100644 --- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml +++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para proteger la batería"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema con el accesorio de carga"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Presiona Menú para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada para la red"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hay ninguna tarjeta SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Introduce una tarjeta SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la tarjeta SIM o no se puede leer. Introduce una tarjeta SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Tarjeta SIM inutilizable."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Tu tarjeta SIM se desactivó permanentemente.\n Ponte en contacto con tu proveedor de servicios inalámbricos para obtener otra tarjeta SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La tarjeta SIM está bloqueada."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La tarjeta SIM está bloqueada con el código PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando tarjeta SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml index 0ea98a8be752..cf7f3d220443 100644 --- a/packages/SystemUI/res-keyguard/values-es/strings.xml +++ b/packages/SystemUI/res-keyguard/values-es/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para proteger la batería"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema con el accesorio de carga"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pulsa el menú para desbloquear la pantalla."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada para la red"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hay ninguna SIM."</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Añade una SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Falta la SIM o no se puede leer. Añade una SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"No se puede usar la SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Tu SIM se ha desactivado de forma permanente.\n Para obtener otra SIM, ponte en contacto con tu proveedor de servicios inalámbricos."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM está bloqueada."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM está bloqueada con el código PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml index 722a02237b3d..6335ca84c75d 100644 --- a/packages/SystemUI/res-keyguard/values-et/strings.xml +++ b/packages/SystemUI/res-keyguard/values-et/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Aeglane laadimine"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laadimine on aku kaitsmiseks optimeeritud"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • probleem laadimistarvikuga"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Vajutage avamiseks menüüklahvi."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Võrk on lukus"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM-i pole"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Lisage SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM puudub või pole loetav. Lisage SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-i ei saa kasutada."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Teie SIM on jäädavalt inaktiveeritud.\n Teise SIM-i saamiseks võtke ühendust oma traadita side teenusepakkujaga."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM on lukustatud."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM on PUK-koodiga lukustatud."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-i avamine …"</string> diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml index d3293696ec58..b47c58a770aa 100644 --- a/packages/SystemUI/res-keyguard/values-eu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mantso kargatzen"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Bateria ez kaltetzeko, kargatzeko modua optimizatu da"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Arazo bat dago kargatzeko osagarriarekin"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Desblokeatzeko, sakatu Menua."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sarea blokeatuta dago"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ez dago SIMik"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Gehitu SIM bat."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIMa falta da, edo ezin da irakurri. Gehitu SIM bat."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ezin da erabili SIMa."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Betiko desaktibatu da SIMa.\n Jarri operadorearekin harremanetan beste SIM bat eskuratzeko."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIMa blokeatuta dago."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIMa PUKaren bidez desblokeatu behar da."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMa desblokeatzen…"</string> diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml index ae3f04a290e4..f274f5fd7a57 100644 --- a/packages/SystemUI/res-keyguard/values-fa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آهستهآهسته شارژ میشود"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • برای محافظت از باتری، شارژ بهینه میشود"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • در شارژ وسیله جانبی مشکلی وجود دارد"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"برای باز کردن قفل روی «منو» فشار دهید."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"شبکه قفل شد"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"سیمکارتی وجود ندارد"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"سیمکارت اضافه کنید."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"سیمکارت موجود نیست یا قابلخواندن نیست. سیمکارت اضافه کنید."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"سیمکارت قابلاستفاده نیست."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"سیمکارت شما برای همیشه غیرفعال شده است.\n برای دریافت سیمکارتی دیگر، با رساننده خدمات بیسیم خود تماس بگیرید."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"سیمکارت قفل است."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"سیمکارت با کد PUK قفل شده است."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"درحال باز کردن قفل سیمکارت…"</string> diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml index 050df9983725..dd9ce2e4453f 100644 --- a/packages/SystemUI/res-keyguard/values-fi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladataan hitaasti"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lataus optimoitu akun suojaamiseksi"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ongelma laturin kanssa"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Poista lukitus painamalla Valikkoa."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Verkko lukittu"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ei SIM-korttia"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Lisää SIM-kortti."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-korttia ei löydy tai ei voi lukea. Lisää SIM-kortti."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-korttia ei voi käyttää."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Sim-kortti on poistettu käytöstä pysyvästi.\n Ota yhteyttä langattoman palvelun tarjoajaan ja pyydä uusi SIM-kortti."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortti on lukittu."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortti on lukittu PUK-koodilla."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortin lukitusta avataan…"</string> diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml index fa1a191b8944..742f56e7f214 100644 --- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"En recharge lente : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge optimisée pour protéger la pile"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problème concernant l\'accessoire de recharge"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur la touche Menu pour déverrouiller l\'appareil."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Aucune carte SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Ajouter une carte SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"La carte SIM est manquante ou illisible. Ajouter une carte SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La carte SIM est inutilisable."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Votre carte SIM a été désactivée de manière permanente.\n Communiquez avec votre fournisseur de services sans fil pour obtenir une autre carte SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La carte SIM est verrouillée."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La carte SIM est verrouillée par clé PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Déverrouillage de la carte SIM en cours…"</string> diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml index d687a1d7e9a2..92d24c4bbc28 100644 --- a/packages/SystemUI/res-keyguard/values-fr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge lente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge optimisée pour protéger la batterie"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problème de recharge de l\'accessoire"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur \"Menu\" pour déverrouiller le clavier."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Aucune SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Ajoutez une SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"La SIM est absente ou illisible. Ajoutez une SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilisable."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Votre SIM a été désactivée définitivement.\n Contactez votre opérateur de téléphonie mobile pour en obtenir une autre."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM verrouillée."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM verrouillée par clé PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Déblocage de la SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml index 68f22cb262f7..4837de2139ac 100644 --- a/packages/SystemUI/res-keyguard/values-gl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para protexer a batería"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema co accesorio de carga"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Preme Menú para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada pola rede"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Non hai ningunha SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Engade unha SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"A SIM falta ou non se pode ler. Engade unha."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"A SIM non se pode usar."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"A SIM desactivouse permanentemente.\n Ponte en contacto co teu fornecedor de servizos sen fíos para conseguir outra."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"A SIM está bloqueada."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"A SIM está bloqueada mediante PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml index 99c988363256..7f8c6d824f93 100644 --- a/packages/SystemUI/res-keyguard/values-gu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ધીમેથી ચાર્જિંગ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • બૅટરીની સુરક્ષા કરવા માટે, ચાર્જિંગ ઑપ્ટિમાઇઝ કરવામાં આવ્યું છે"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ચાર્જિંગ ઍક્સેસરીમાં સમસ્યા"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"અનલૉક કરવા માટે મેનૂ દબાવો."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"નેટવર્ક લૉક થયું"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"કોઈ સિમ કાર્ડ નથી"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"સિમ કાર્ડ ઉમેરો."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"સિમ કાર્ડ ખૂટે છે અથવા વાંચી શકાય એવું નથી. સિમ કાર્ડ ઉમેરો."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ઉપયોગમાં ન લઈ શકાતું સિમ કાર્ડ."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"તમારું સિમ કાર્ડ કાયમ માટે નિષ્ક્રિય કરવામાં આવ્યું છે.\n બીજા સિમ કાર્ડ માટે તમારા વાયરલેસ સેવા પ્રદાતાનો સંપર્ક કરો."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"સિમ કાર્ડ લૉક કરેલું છે."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"સિમ કાર્ડ PUK-લૉક કરેલું છે."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"સિમ કાર્ડ અનલૉક કરી રહ્યાં છીએ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml index 9d32f04206dc..18d63ab35e94 100644 --- a/packages/SystemUI/res-keyguard/values-hi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • धीरे चार्ज हो रहा है"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बैटरी को नुकसान से बचाने के लिए, चार्जिंग को ऑप्टिमाइज़ किया गया"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जर ऐक्सेसरी से जुड़ी समस्या"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"लॉक खोलने के लिए मेन्यू दबाएं."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक किया हुआ है"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"कोई सिम नहीं है"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"कोई सिम जोड़ें."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"सिम मौजूद नहीं है या उसे ऐक्सेस नहीं किया जा सकता. कोई सिम जोड़ें."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"सिम को हमेशा के लिए बंद कर दिया गया है."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"आपका सिम हमेशा के लिए बंद कर दिया गया है.\n दूसरा सिम पाने के लिए, वायरलेस सेवा देने वाली कंपनी से संपर्क करें."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"सिम लॉक है."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"सिम में PUK लॉक लगा है."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"सिम अनलॉक हो रहा है…"</string> diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml index b4224bfbc497..0206faf7f2ad 100644 --- a/packages/SystemUI/res-keyguard/values-hr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • sporo punjenje"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje se optimizira radi zaštite baterije"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem s priborom za punjenje"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite Izbornik da biste otključali."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Dodajte SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM nedostaje ili nije čitljiv. Dodajte SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM je neupotrebljiv."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaš je SIM trajno deaktiviran.\n Obratite se svom davatelju bežičnih usluga da biste dobili drugi SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM je zaključan."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM je zaključan PUK-om."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Otključavanje SIM-a…"</string> diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml index bc712c7cc882..8575e10884ce 100644 --- a/packages/SystemUI/res-keyguard/values-hu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lassú töltés"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimalizált töltés az akkumulátor védelme érdekében"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Probléma van a töltőtartozékkal"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"A feloldáshoz nyomja meg a Menü gombot."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Hálózat zárolva"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nincs SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Adjon hozzá egy SIM-et."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"A SIM hiányzik vagy nem olvasható. Adjon hozzá egy SIM-et."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nem használható SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM véglegesen deaktiválva.\n Forduljon a vezeték nélküli szolgáltatójához másik SIM beszerzése érdekében."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Zárolt SIM."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"A SIM le van zárva PUK-kóddal."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM zárolásának feloldása…"</string> diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml index 4d7bbbef2a08..a7c3abab1d84 100644 --- a/packages/SystemUI/res-keyguard/values-hy/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Դանդաղ լիցքավորում"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Մարտկոցը պաշտպանելու համար լիցքավորումն օպտիմալացվել է"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Լիցքավորիչի հետ կապված խնդիր"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ապակողպելու համար սեղմեք Ընտրացանկը:"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Ցանցը կողպված է"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM քարտ չկա"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Ավելացրեք SIM քարտ։"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM քարտը բացակայում է կամ ընթեռնելի չէ։ Ավելացրեք SIM քարտ։"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Անվավեր SIM քարտ։"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ձեր SIM քարտն ընդմիշտ ապակտիվացվել է։\n Նոր SIM քարտ ձեռք բերելու համար կապվեք ձեր բջջային օպերատորի հետ։"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM քարտը կողպված է։"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM քարտը կողպված է PUK կոդով։"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM քարտն ապակողպվում է…"</string> diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml index aa766e963670..f9a840fc5aa8 100644 --- a/packages/SystemUI/res-keyguard/values-in/strings.xml +++ b/packages/SystemUI/res-keyguard/values-in/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengisi daya dengan lambat"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengisian daya dioptimalkan untuk melindungi baterai"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Masalah dengan aksesori pengisian daya"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tekan Menu untuk membuka kunci."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Jaringan terkunci"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Tidak ada SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Tambahkan SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM tidak ada atau tidak dapat dibaca. Tambahkan SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak dapat digunakan."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM Anda telah dinonaktifkan secara permanen.\n Hubungi penyedia layanan nirkabel Anda untuk mendapatkan SIM lain."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM dikunci."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM dikunci PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Membuka kunci SIM …"</string> diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml index 99f177939378..b7147c201919 100644 --- a/packages/SystemUI/res-keyguard/values-is/strings.xml +++ b/packages/SystemUI/res-keyguard/values-is/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hæg hleðsla"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hleðsla fínstillt til að vernda rafhlöðuna"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Vandamál með hleðslubúnað"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ýttu á valmyndarhnappinn til að taka úr lás."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Net læst"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ekkert SIM-kort"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Bæta við SIM-korti."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kort vantar eða er ekki læsilegt. Bæta við SIM-korti."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ónothæft SIM-kort."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-kortið þitt var gert varanlega óvirkt.\n Hafðu samband við símafyrirtækið þitt til að fá nýtt SIM-kort."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kort er læst."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kort er læst með PUK-númeri."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Opnar SIM-kort…"</string> diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml index cc0a16480547..9e1b18740750 100644 --- a/packages/SystemUI/res-keyguard/values-it/strings.xml +++ b/packages/SystemUI/res-keyguard/values-it/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica lenta"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica ottimizzata per proteggere la batteria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema relativo all\'accessorio di ricarica"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Premi Menu per sbloccare."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rete bloccata"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nessuna SIM presente"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Aggiungi una SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM mancante o non leggibile. Aggiungi una SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizzabile."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"La SIM è stata disattivata definitivamente.\n Contatta il tuo fornitore di servizi wireless per richiedere un\'altra SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"La SIM è bloccata."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"La SIM è bloccata tramite PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Sblocco della SIM in corso…"</string> diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml index 00c717c3d3c2..16316cebcc7c 100644 --- a/packages/SystemUI/res-keyguard/values-iw/strings.xml +++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • בטעינה איטית"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • הטעינה עברה אופטימיזציה כדי להגן על הסוללה"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • יש בעיה עם אביזר הטעינה"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"יש ללחוץ על \'תפריט\' כדי לבטל את הנעילה."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"הרשת נעולה"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"אין כרטיס SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"הוספת כרטיס SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"כרטיס ה-SIM חסר או שלא ניתן לקרוא אותו. הוספת כרטיס SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"לא ניתן להשתמש בכרטיס ה-SIM הזה."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"כרטיס ה-SIM שלך הושבת באופן סופי.\n עליך לפנות לספק השירות האלחוטי שלך לקבלת כרטיס SIM אחר."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"כרטיס ה-SIM נעול."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"כרטיס ה-SIM נעול באמצעות PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"מתבצע ביטול נעילה של כרטיס ה-SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml index 1d59a630b8c2..6e8f42369e0a 100644 --- a/packages/SystemUI/res-keyguard/values-ja/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 低速充電中"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • バッテリーを保護するために、充電が最適化されています"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電用アクセサリに関する問題"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"メニューからロックを解除できます。"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ネットワークがロックされました"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM がありません"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM を追加してください。"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM が見つからないか読み取れません。SIM を追加してください。"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM が使用できません。"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM が完全に無効になっています。\n ワイヤレス サービス プロバイダにお問い合わせのうえ、新しい SIM を入手してください。"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM がロックされています。"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM が PUK でロックされました。"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM ロックを解除しています…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml index 5bd6b2e3d883..a31243d9f5f9 100644 --- a/packages/SystemUI/res-keyguard/values-ka/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ნელა იტენება"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • დატენვა ოპტიმიზირებულია ბატარეის დასაცავად"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • დამტენი დამხმარე მოწყობილობის პრობლემა"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"განსაბლოკად დააჭირეთ მენიუს."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ქსელი ჩაკეტილია"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM არ არის"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM-ის დამატება."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM აკლია ან არ იკითხება. SIM-ის დამატება."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"გამოუყენებელი SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"თქვენი SIM სამუდამოდ გამორთულია.\n დაუკავშირდით თქვენს უკაბელო სერვისის პროვაიდერს სხვა SIM ბარათისთვის."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-ბარათი ჩაკეტილია."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM დაბლოკილია PUK-ით."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-ის განბლოკვა…"</string> diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml index 83d270ded60c..6a777832d07d 100644 --- a/packages/SystemUI/res-keyguard/values-kk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Баяу зарядталуда"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны қорғау үшін зарядтау оңтайландырылды"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядтау құрылғысына қатысты мәселе туындады."</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ашу үшін \"Мәзір\" пернесін басыңыз."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Желі құлыптаулы"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM картасы жоқ."</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM картасын қосыңыз."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM картасы жоқ немесе оқылмай тұр. SIM картасын қосыңыз."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM картасын пайдалану мүмкін емес."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картаңыз біржола өшірілді.\n Сымсыз байланыс провайдеріне хабарласып, басқа SIM картасын алыңыз."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM картасы құлыпталған."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM картасы PUK кодымен құлыпталды."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картасының құлпы ашылып жатыр…"</string> diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml index 5306cb1ff4e6..cda9520862aa 100644 --- a/packages/SystemUI/res-keyguard/values-km/strings.xml +++ b/packages/SystemUI/res-keyguard/values-km/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • កំពុងសាកថ្មយឺត"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • បានបង្កើនប្រសិទ្ធភាពនៃការសាក ដើម្បីការពារថ្ម"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • បញ្ហាពាក់ព័ន្ធនឹងគ្រឿងសាកថ្ម"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ចុចម៉ឺនុយ ដើម្បីដោះសោ។"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"បណ្ដាញជាប់សោ"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"គ្មានស៊ីមទេ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"បញ្ចូលស៊ីម។"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"បាត់ស៊ីម ឬមិនអាចអានស៊ីមបាន។ បញ្ចូលស៊ីម។"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ស៊ីមមិនអាចប្រើបាន។"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ស៊ីមរបស់អ្នកត្រូវបានបិទដំណើរការជាអចិន្ត្រៃយ៍។\n ទាក់ទងទៅក្រុមហ៊ុនផ្ដល់សេវាឥតខ្សែរបស់អ្នក ដើម្បីទទួលបានស៊ីមមួយទៀត។"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ស៊ីមត្រូវបានចាក់សោ។"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ស៊ីមត្រូវបានចាក់សោដោយ PUK។"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"កំពុងដោះសោស៊ីម…"</string> diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml index d609a23d2c87..e24005a08fc9 100644 --- a/packages/SystemUI/res-keyguard/values-kn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಚಾರ್ಜಿಂಗ್ ಪರಿಕರ ಕುರಿತು ಸಮಸ್ಯೆ ಇದೆ"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ನೆಟ್ವರ್ಕ್ ಲಾಕ್ ಆಗಿದೆ"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM ಇಲ್ಲ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM ಅನ್ನು ಸೇರಿಸಿ."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM ಕಾಣೆಯಾಗಿದೆ ಅಥವಾ ರೀಡ್ ಆಗುತ್ತಿಲ್ಲ. SIM ಅನ್ನು ಸೇರಿಸಿ."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ನಿಷ್ಪ್ರಯೋಜಕವಾಗಿದೆ."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ನಿಮ್ಮ SIM ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.\n ಬೇರೊಂದು SIM ಗಾಗಿ ನಿಮ್ಮ ವೈರ್ಲೆಸ್ ಸೇವಾ ಪೂರೈಕೆದಾರರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM ಲಾಕ್ ಆಗಿದೆ."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK ಲಾಕ್ ಆಗಿದೆ."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml index 0e09fade9d57..7378cc78e5e6 100644 --- a/packages/SystemUI/res-keyguard/values-ko/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 저속 충전 중"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 배터리 보호를 위해 충전 최적화됨"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 충전 액세서리 문제"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"잠금 해제하려면 메뉴를 누르세요."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"네트워크 잠김"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM 없음"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM을 추가하세요."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM이 없거나 SIM을 읽을 수 없습니다. SIM을 추가하세요."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM을 사용할 수 없습니다."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM이 영구적으로 비활성화되었습니다.\n 다른 SIM을 사용하려면 무선 서비스 제공업체에 문의하시기 바랍니다."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM이 잠김 상태입니다."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM이 PUK 잠김 상태입니다."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM 잠금 해제 중…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml index 1e03c03b1ae4..88f0b97b385b 100644 --- a/packages/SystemUI/res-keyguard/values-ky/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Жай кубатталууда"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны коргоо үчүн кубаттоо процесси оптималдаштырылды"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Кубаттоочу шайманда көйгөй бар"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Кулпуну ачуу үчүн Менюну басыңыз."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Тармак кулпуланган"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM карта жок"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM карта кошуңуз."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM карта жок же окулбайт. SIM карта кошуңуз."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Жараксыз SIM карта."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM картаңыз биротоло өчүрүлдү.\n Башка SIM карта алуу үчүн зымсыз кызмат көрсөтүүчүгө кайрылыңыз."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM карта кулпуланган."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM карта PUK менен кулпуланган."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM картанын кулпусу ачылууда…"</string> diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml index 0059d7fbacee..00a382a7cd02 100644 --- a/packages/SystemUI/res-keyguard/values-lo/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ກຳລັງສາກແບບຊ້າ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ການສາກຖືກປັບໃຫ້ເໝາະສົມເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ບັນຫາກັບອຸປະກອນເສີມໃນການສາກ"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ກົດ \"ເມນູ\" ເພື່ອປົດລັອກ."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ເຄືອຂ່າຍຖືກລັອກ"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ບໍ່ມີຊິມ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ເພີ່ມຊິມ."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ບໍ່ມີຊິມ ຫຼື ອ່ານຊິມບໍ່ໄດ້. ເພີ່ມຊິມ."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ຊິມໃຊ້ບໍ່ໄດ້."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ຊິມຂອງທ່ານຖືກປິດໃຊ້ຢ່າງຖາວອນແລ້ວ.\n ຕິດຕໍ່ຜູ້ໃຫ້ບໍລິການໂທລະສັບໄຮ້ສາຍຂອງທ່ານເພື່ອຂໍຊິມໃໝ່."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ຊິມຖືກລັອກຢູ່."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ຊິມຖືກລັອກດ້ວຍ PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ກຳລັງປົດລັອກຊິມ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml index 01e2f8820700..31c410790556 100644 --- a/packages/SystemUI/res-keyguard/values-lt/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lėtai įkraunama"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Įkrovimas optimizuotas siekiant apsaugoti akumuliatorių"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Su įkrovimo priedu susijusi problema"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Paspauskite meniu, jei norite atrakinti."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tinklas užrakintas"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nėra SIM kortelės"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Įdėkite SIM kortelę."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Trūksta SIM kortelės arba ji neskaitoma. Įdėkite SIM kortelę."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nenaudojama SIM kortelė."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jūsų SIM kortelė visam laikui išjungta.\n Susisiekite su belaidžio ryšio paslaugos teikėju, kad gautumėte naują SIM kortelę."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kortelė užrakinta."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM kortelė užrakinta PUK kodu."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Atrakinama SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml index 2133694f99a8..ecf223351942 100644 --- a/packages/SystemUI/res-keyguard/values-lv/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Notiek lēnā uzlāde"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Uzlāde optimizēta, lai saudzētu akumulatoru"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problēma ar uzlādes ierīci"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Lai atbloķētu, nospiediet izvēlnes ikonu."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tīkls ir bloķēts."</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nav SIM kartes"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Pievienojiet SIM karti."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Nav SIM kartes, vai arī to nevar nolasīt. Pievienojiet SIM karti."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM karte nav izmantojama."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Jūsu SIM karte ir neatgriezeniski deaktivizēta.\n Sazinieties ar savu bezvadu pakalpojumu sniedzēju, lai iegūtu citu SIM karti."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karte ir bloķēta."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karte ir bloķēta ar PUK kodu."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Notiek SIM kartes atbloķēšana…"</string> diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml index 2771c7f5b5d6..3f089b991b92 100644 --- a/packages/SystemUI/res-keyguard/values-mk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Бавно полнење"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Полнењето е оптимизирано за да се заштити батеријата"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем со додатокот за полнење"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Притиснете „Мени“ за отклучување."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заклучена"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-картичка"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додајте SIM-картичка."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Нема SIM-картичка или не може да се прочита. Додајте SIM-картичка."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-картичката е неупотреблива."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Вашата SIM-картичка е трајно деактивирана.\n Контактирајте со давателот на услуги за безжична мрежа за друга SIM-картичка."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-картичката е заклучена."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-картичката е заклучена со PUK-код."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Се отклучува SIM-картичката…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml index 02ee66fc83b4..be1ea89688ff 100644 --- a/packages/SystemUI/res-keyguard/values-ml/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഒപ്റ്റിമൈസ് ചെയ്തു"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ചാർജിംഗ് ആക്സസറിയുമായി ബന്ധപ്പെട്ട പ്രശ്നം"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"അൺലോക്കുചെയ്യാൻ മെനു അമർത്തുക."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"നെറ്റ്വർക്ക് ലോക്കുചെയ്തു"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"സിം ഇല്ല"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"സിം ചേർക്കുക."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"സിം കാണുന്നില്ല അല്ലെങ്കിൽ റീഡ് ചെയ്യാനായില്ല. സിം ചേർക്കുക."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ഉപയോഗശൂന്യമായ സിം."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"നിങ്ങളുടെ സിം ശാശ്വതമായി നിഷ്ക്രിയമാക്കി.\n മറ്റൊരു സിമ്മിന് നിങ്ങളുടെ വയർലെസ് സേവന ദാതാവിനെ ബന്ധപ്പെടുക."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"സിം ലോക്ക് ചെയ്തു."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"സിം PUK ലോക്ക് ചെയ്തു."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"സിം അൺലോക്ക് ചെയ്യുന്നു…"</string> diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml index 2b9f81e9e358..54fdecd761ac 100644 --- a/packages/SystemUI/res-keyguard/values-mn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Удаан цэнэглэж байна"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батарейг хамгаалахын тулд цэнэглэх явцыг оновчилсон"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Цэнэглэх хэрэгсэлд асуудал гарлаа"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Түгжээг тайлах бол цэсийг дарна уу."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сүлжээ түгжигдсэн"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM байхгүй"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM нэмнэ үү."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM дутуу эсвэл үүнийг унших боломжгүй байна. SIM нэмнэ үү."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ашиглах боломжгүй SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Таны SIM-г бүрмөсөн идэвхгүй болгосон байна.\n Өөр SIM авах бол утасгүй үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-г түгжсэн байна."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-г PUK-р түгжсэн байна."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-н түгжээг тайлж байна…"</string> diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml index 7aa7bdd26c08..eff4c7a9b2d3 100644 --- a/packages/SystemUI/res-keyguard/values-mr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • सावकाश चार्ज होत आहे"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बॅटरीचे संरक्षण करण्यासाठी चार्जिंग ऑप्टिमाइझ केले आहे"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जिंगच्या ॲक्सेसरीसंबंधित समस्या"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"अनलॉक करण्यासाठी मेनू प्रेस करा."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक केले"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"सिम नाही"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"सिम जोडा."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"सिम गहाळ झाले आहे किंवा ते रीड करू शकत नाही. सिम जोडा."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"वापरण्यायोग्य नसलेले सिम."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"तुमचे सिम कायमचे डीॲक्टिव्हेट केले गेले आहे.\n दुसऱ्या सिमसाठी तुमच्या वायरलेस सेवा पुरवठादाराशी संपर्क साधा."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"सिम लॉक केलेले आहे."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"सिम PUK लॉक केलेले आहे."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"सिम अनलॉक करत आहे…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml index bdfa4a77eb8a..d9eb4ca07c6d 100644 --- a/packages/SystemUI/res-keyguard/values-ms/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengecas dengan perlahan"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengecasan dioptimumkan untuk melindungi bateri"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Isu berkaitan aksesori pengecasan"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Tekan Menu untuk membuka kunci."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rangkaian dikunci"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Tiada SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Tambah SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM tiada atau tidak boleh dibaca. Tambah SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak boleh digunakan."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM anda telah dinyahaktifkan secara kekal.\n Hubungi penyedia perkhidmatan wayarles anda untuk mendapatkan SIM lain."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM dikunci."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM dikunci PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Membuka kunci SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml index 576250b4f157..afbce26a945f 100644 --- a/packages/SystemUI/res-keyguard/values-my/strings.xml +++ b/packages/SystemUI/res-keyguard/values-my/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည်"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ဘက်ထရီကာကွယ်ရန် အားသွင်းခြင်းကို အကောင်းဆုံးပြင်ဆင်ထားသည်"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • အားသွင်းပစ္စည်းတွင် ပြဿနာရှိသည်"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"မီနူးကို နှိပ်၍ လော့ခ်ဖွင့်ပါ။"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ကွန်ရက်ကို လော့ခ်ချထားသည်"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ဆင်းမ်ကတ် မရှိပါ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ဆင်းမ်ကတ်ထည့်ပါ။"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ဆင်းမ်မရှိပါ (သို့) သုံး၍မရပါ။ ဆင်းမ်ကတ်ထည့်ပါ။"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ဆင်းမ်ကတ်ကို သုံး၍မရပါ။"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"သင်၏ဆင်းမ်ကတ်ကို အပြီးပိတ်လိုက်သည်။\n ဆင်းမ်ကတ်နောက်တစ်ခု ရယူရန် သင်၏ ကြိုးမဲ့ဝန်ဆောင်မှုပေးသူထံ ဆက်သွယ်ပါ။"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ဆင်းမ်ကတ်ကို လော့ခ်ချထားသည်။"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ဆင်းမ်ကတ်၏ ပင်နံပါတ်ပြန်ဖွင့်သည့် ကုဒ်ကို လော့ခ်ချထားသည်။"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ဆင်းမ်ကတ် ဖွင့်နေသည်…"</string> diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml index 455d08620fbb..3098e878f5b6 100644 --- a/packages/SystemUI/res-keyguard/values-nb/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lader sakte"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladingen er optimalisert for å beskytte batteriet"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem med ladetilbehøret"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Trykk på menyknappen for å låse opp."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Nettverket er låst"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ingen SIM-kort"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Legg til et SIM-kort."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kortet mangler eller kan ikke leses. Legg til et SIM-kort."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet kan ikke brukes."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-kortet er deaktivert permanent.\n Kontakt leverandøren av trådløstjenesten for å få et nytt SIM-kort."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet er låst."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet er låst med PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Låser opp SIM-kortet …"</string> diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml index f0094a3d427d..45b88197a52d 100644 --- a/packages/SystemUI/res-keyguard/values-ne/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • मन्द गतिमा चार्ज गरिँदै"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ब्याट्री जोगाउन चार्ज गर्ने प्रक्रिया अप्टिमाइज गरिएको छ"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्ज गर्ने एक्सेसरीमा कुनै समस्या आयो"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"अनलक गर्न मेनु थिच्नुहोस्।"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लक भएको छ"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM कार्ड हालिएको छैन"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM कार्ड हाल्नुहोस्।"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM कार्ड हालिएको छैन वा रिड गर्न मिल्दैन। SIM कार्ड हाल्नुहोस्।"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"यो SIM कार्ड प्रयोग गर्न मिल्दैन।"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"तपाईंको SIM कार्ड सदाका लागि डिएक्टिभेट गरिएको छ।\n आफ्नो वायरलेस सेवा प्रदायकलाई सम्पर्क गरी अर्को SIM कार्ड प्राप्त गर्नुहोस्।"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM कार्ड लक गरिएको छ।"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM कार्ड PUK प्रयोग गरी लक गरिएको छ।"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM कार्ड अनलक गरिँदै छ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml index 1ba4a814ebde..af24d4002e0e 100644 --- a/packages/SystemUI/res-keyguard/values-nl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Langzaam opladen"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladen geoptimaliseerd om de batterij te beschermen"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Probleem met oplaadaccessoire"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Druk op Menu om te ontgrendelen."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netwerk vergrendeld"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Geen simkaart"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Voeg een simkaart toe."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"De simkaart ontbreekt of kan niet worden gelezen. Voeg een simkaart toe."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare simkaart."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Je simkaart is permanent gedeactiveerd.\n Neem contact op met je mobiele serviceprovider voor een nieuwe simkaart."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Simkaart is vergrendeld."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Simkaart is vergrendeld met pukcode."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Simkaart ontgrendelen…"</string> diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml index b31c9c0c2207..8cae9874bbf3 100644 --- a/packages/SystemUI/res-keyguard/values-or/strings.xml +++ b/packages/SystemUI/res-keyguard/values-or/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂକୁ ଅପ୍ଟିମାଇଜ କରାଯାଇଛି"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଚାର୍ଜିଂ ଆକସେସୋରୀ ସହ ସମସ୍ୟା"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ଅନଲକ୍ କରିବା ପାଇଁ ମେନୁକୁ ଦବାନ୍ତୁ।"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ନେଟୱର୍କକୁ ଲକ୍ କରାଯାଇଛି"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"କୌଣସି SIM ନାହିଁ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ଏକ SIM ଯୋଗ କରନ୍ତୁ।"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM ଉପଲବ୍ଧ ନାହିଁ କିମ୍ବା ପଢ଼ିପାରିବା ଯୋଗ୍ୟ ନୁହେଁ। ଏକ SIM ଯୋଗ କରନ୍ତୁ।"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ବ୍ୟବହାର ଅଯୋଗ୍ୟ ଥିବା SIM।"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ଆପଣଙ୍କ SIMକୁ ସ୍ଥାୟୀ ଭାବରେ ନିଷ୍କ୍ରିୟ କରାଯାଇଛି।\n ଅନ୍ୟ ଏକ SIM ପାଇଁ ଆପଣଙ୍କ ୱେୟାରଲେସ ସେବା ପ୍ରଦାନକାରୀଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ।"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIMକୁ ଲକ କରାଯାଇଛି।"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIMକୁ PUK-ଲକ କରାଯାଇଛି।"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMକୁ ଅନଲକ କରାଯାଉଛି…"</string> diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml index 209b63fbd8f8..18959c8fb9e8 100644 --- a/packages/SystemUI/res-keyguard/values-pa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਹੌਲੀ-ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਸੁਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਚਾਰਜ ਕਰਨ ਵਾਲੀ ਐਕਸੈਸਰੀ ਸੰਬੰਧੀ ਸਮੱਸਿਆ"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"ਅਣਲਾਕ ਕਰਨ ਲਈ \"ਮੀਨੂ\" ਦਬਾਓ।"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ਨੈੱਟਵਰਕ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ਕੋਈ ਸਿਮ ਨਹੀਂ ਹੈ"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ਸਿਮ ਸ਼ਾਮਲ ਕਰੋ।"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ਸਿਮ ਮੌਜੂਦ ਨਹੀਂ ਹੈ ਜਾਂ ਪੜ੍ਹਨਯੋਗ ਨਹੀਂ ਹੈ। ਸਿਮ ਸ਼ਾਮਲ ਕਰੋ।"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ਬੇਕਾਰ ਸਿਮ।"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ਤੁਹਾਡੇ ਸਿਮ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਅਕਿਰਿਆਸ਼ੀਲ ਕੀਤਾ ਗਿਆ ਹੈ।\n ਦੂਜੇ ਸਿਮ ਲਈ ਆਪਣੇ ਵਾਇਰਲੈੱਸ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"ਸਿਮ ਲਾਕ ਹੈ।"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"ਸਿਮ PUK-ਲਾਕ ਹੈ।"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"ਸਿਮ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string> diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml index 7ec988ecf777..bd00ba9bb5f6 100644 --- a/packages/SystemUI/res-keyguard/values-pl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wolne ładowanie"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ładowanie zoptymalizowane w celu ochrony baterii"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem z akcesoriami do ładowania"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Naciśnij Menu, aby odblokować."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sieć zablokowana"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Brak karty SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Dodaj kartę SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Brak karty SIM lub nie można jej odczytać. Dodaj kartę SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nie można użyć karty SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Karta SIM została trwale wyłączona.\n Skontaktuj się z dostawcą usług bezprzewodowych, aby uzyskać inną kartę SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Karta SIM jest zablokowana."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Karta SIM została zablokowana kodem PUK"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odblokowuję kartę SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml index 78a80912e99e..54e270f8eb2c 100644 --- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando lentamente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pressione Menu para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem chip"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Adicione um chip."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O chip não foi inserido ou não pode ser lido. Adicione um chip."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Seu chip foi desativado permanentemente.\n Entre em contato com seu provedor de serviços sem fio para receber outro chip."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O chip está bloqueado."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O chip está bloqueado pela PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando chip…"</string> diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml index 5549b36e504c..2e37bde883de 100644 --- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • A carregar lentamente…"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Prima Menu para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Adicione um SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O SIM está em falta ou não é legível. Adicione um SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizável."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"O SIM foi desativado permanentemente.\n Contacte o seu fornecedor de serviços de rede sem fios para obter outro SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O SIM está bloqueado."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O SIM está bloqueado com o PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"A desbloquear SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml index 78a80912e99e..54e270f8eb2c 100644 --- a/packages/SystemUI/res-keyguard/values-pt/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando lentamente"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pressione Menu para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem chip"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Adicione um chip."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"O chip não foi inserido ou não pode ser lido. Adicione um chip."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Seu chip foi desativado permanentemente.\n Entre em contato com seu provedor de serviços sem fio para receber outro chip."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"O chip está bloqueado."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"O chip está bloqueado pela PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Desbloqueando chip…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml index df28b8d10101..ead092099326 100644 --- a/packages/SystemUI/res-keyguard/values-ro/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Se încarcă lent"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Încărcarea este optimizată pentru a proteja bateria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problemă legată de accesoriul de încărcare"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Apasă pe Meniu pentru a debloca."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rețea blocată"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Niciun card SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Adaugă un card SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Cardul SIM lipsește sau nu poate fi citit. Adaugă un card SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Cardul SIM nu se poate folosi."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Cardul tău SIM a fost dezactivat definitiv.\n Contactează furnizorul de servicii wireless pentru a obține un alt card SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Cardul SIM este blocat."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Cardul SIM este blocat prin cod PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Se deblochează cardul SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml index 62249a1a68c7..595fba5dfcb2 100644 --- a/packages/SystemUI/res-keyguard/values-ru/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"Идет медленная зарядка (<xliff:g id="PERCENTAGE">%s</xliff:g>)"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядка оптимизирована для защиты батареи"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблема с зарядным устройством"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Для разблокировки нажмите \"Меню\"."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сеть заблокирована"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM-карта отсутствует"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Добавьте SIM-карту."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта отсутствует или не распознана. Добавьте SIM-карту."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-карту невозможно использовать."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-карта была окончательно деактивирована.\n Чтобы получить новую, обратитесь к своему оператору мобильной связи."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карта заблокирована."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карта заблокирована с помощью PUK-кода."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Разблокировка SIM-карты…"</string> diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml index 17ced75aaf1b..b6a742234fe0 100644 --- a/packages/SystemUI/res-keyguard/values-si/strings.xml +++ b/packages/SystemUI/res-keyguard/values-si/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • සෙමින් ආරෝපණය වෙමින්"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය ප්රශස්ත කර ඇත"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ආරෝපණ උපාංගයේ ගැටලුව"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"අගුලු හැරීමට මෙනුව ඔබන්න."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"ජාලය අගුළු දමා ඇත"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM නැත"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM එකක් එක් කරන්න."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM අස්ථානගතයි හෝ කියවිය නොහැක. SIM එකක් එක් කරන්න."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"භාවිත කළ නොහැකි SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ඔබේ SIM ස්ථිරවම අක්රිය කර ඇත.\n වෙනත් SIM පතක් සඳහා ඔබේ රැහැන් රහිත සේවා සපයන්නා අමතන්න."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM අගුළු දමා ඇත."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK-අගුළු දමා ඇත."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM අගුළු අරිමින්…"</string> diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml index ef08a6cd5258..5e34a94ffccd 100644 --- a/packages/SystemUI/res-keyguard/values-sk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíja sa pomaly"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíjanie je optimalizované, aby sa chránila batéria"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problém s nabíjacím príslušenstvom"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Odomknete stlačením tlačidla ponuky."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sieť je zablokovaná"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Žiadna SIM karta"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Pridajte SIM kartu."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta chýba alebo sa nedá čítať. Pridajte SIM kartu."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nepoužiteľná SIM karta."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaša SIM karta bola natrvalo deaktivovaná.\n Požiadajte svojho poskytovateľa bezdrôtových služieb o ďalšiu SIM kartu."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta je uzamknutá."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta je uzamknutá kódom PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM karta sa odomyká…"</string> diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml index a42989c18ea9..3508f3b3af6e 100644 --- a/packages/SystemUI/res-keyguard/values-sl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • počasno polnjenje"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Polnjenje je optimizirano zaradi zaščite baterije"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • težava s pripomočkom za polnjenje"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Če želite odkleniti, pritisnite meni."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Omrežje je zaklenjeno"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ni kartice SIM."</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Dodajte kartico SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Ni kartice SIM ali je ni mogoče prebrati. Dodajte kartico SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartica SIM je neuporabna."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Vaša kartica SIM je bila trajno deaktivirana.\n Za drugo kartico SIM se obrnite na ponudnika brezžičnih storitev."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Kartica SIM je zaklenjena."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Kartica SIM je zaklenjena s kodo PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Odklepanje kartice SIM …"</string> diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml index ce53b7ed3a98..8d71b0f941b7 100644 --- a/packages/SystemUI/res-keyguard/values-sq/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet ngadalë"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Karikimi u optimizua për të mbrojtur baterinë"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem me aksesorin e karikimit"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Shtyp \"Meny\" për të shkyçur."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rrjeti është i kyçur"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nuk ka kartë SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Shto një kartë SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Karta SIM mungon ose është e palexueshme. Shto një kartë SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartë SIM e papërdorshme."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Karta jote SIM është çaktivizuar përgjithmonë.\n Kontakto me ofruesin e shërbimit wireless për një tjetër kartë SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Karta SIM është e kyçur."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Karta SIM është e kyçur me PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Karta SIM po shkyçet…"</string> diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml index 437018d93879..409395285343 100644 --- a/packages/SystemUI/res-keyguard/values-sr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Споро се пуни"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Пуњење је оптимизовано да би се заштитила батерија"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем са додатним прибором за пуњење"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Притисните Мени да бисте откључали."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежа је закључана"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-а"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додајте SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM недостаје или не може да се прочита. Додајте SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неупотребљив SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM је трајно деактивиран.\n Обратите се добављачу услуге бежичне телефоније да бисте добили други SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM је закључан."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM је закључан PUK-ом."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Откључава се SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml index b4b1996094bc..5b01f39a6a11 100644 --- a/packages/SystemUI/res-keyguard/values-sv/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddas långsamt"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddningen har optimerats för att skydda batteriet"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ett problem uppstod med att ladda tillbehöret"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Lås upp genom att trycka på Meny."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Nätverk låst"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Inget SIM-kort"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Lägg till ett SIM-kort."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-kort saknas eller går inte att läsa. Lägg till ett SIM-kort."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet går inte att använda."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Ditt SIM-kort har inaktiverats permanent.\n Kontakta din operatör och be om ett nytt SIM-kort."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-kortet är låst."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-kortet har låsts med PUK-kod."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM-kortet låses upp …"</string> diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml index 8ca90465ee3a..72f1fc31c3da 100644 --- a/packages/SystemUI/res-keyguard/values-sw/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Inachaji pole pole"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hali ya kuchaji imeboreshwa ili kulinda betri"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kifuasi cha kuchaji kina hitilafu"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Bonyeza Menyu ili kufungua."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mtandao umefungwa"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Hakuna SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Weka SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM haipo au haiwezi kusomwa. Weka SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM haiwezi kutumika."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM kadi yako imefungwa kabisa.\n wasiliana na mtoa huduma wako wa pasi waya ili upate SIM nyingine."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM imefungwa."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM imefungwa kwa PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Inafungua SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml index 7671194c3b14..20eb8ef3e43e 100644 --- a/packages/SystemUI/res-keyguard/values-ta/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • மெதுவாகச் சார்ஜாகிறது"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • பேட்டரியைப் பாதுகாக்க சார்ஜிங் மேம்படுத்தப்பட்டுள்ளது"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • சார்ஜரில் சிக்கல் உள்ளது"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"அன்லாக் செய்ய மெனுவை அழுத்தவும்."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"நெட்வொர்க் பூட்டப்பட்டது"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"சிம் இல்லை"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"சிம்மைச் சேருங்கள்."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"சிம் இல்லை அல்லது படிக்கக்கூடியதாக இல்லை. சிம்மைச் சேருங்கள்."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"பயன்படுத்த முடியாத சிம்."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"உங்கள் சிம் நிரந்தரமாக முடக்கப்பட்டுள்ளது.\n மற்றொரு சிம்மிற்கான உங்கள் வயர்லெஸ் சேவை வழங்குநரைத் தொடர்புகொள்ளுங்கள்."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"சிம் லாக் செய்யப்பட்டுள்ளது."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"சிம் PUK-லாக் செய்யப்பட்டுள்ளது."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"சிம்மை அன்லாக் செய்கிறது…"</string> diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml index 623b589861a9..d496944798f9 100644 --- a/packages/SystemUI/res-keyguard/values-te/strings.xml +++ b/packages/SystemUI/res-keyguard/values-te/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • బ్యాటరీని రక్షించడానికి ఛార్జింగ్ ఆప్టిమైజ్ చేయబడింది"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ఛార్జింగ్ యాక్సెసరీతో సమస్య ఉంది"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"అన్లాక్ చేయడానికి మెనూను నొక్కండి."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"నెట్వర్క్ లాక్ చేయబడింది"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM లేదు"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIMను జోడించండి."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM మిస్ అయ్యింది లేదా ఆమోదయోగ్యం కాదు. SIMను జోడించండి."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"వినియోగించలేని SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"మీ SIM శాశ్వతంగా డీయాక్టివేట్ చేయబడింది.\n మరో SIMను పొందడం కోసం మీ వైర్లెస్ సర్వీస్ ప్రొవైడర్ను సంప్రదించండి."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM లాక్ చేయబడింది."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM PUK లాక్ చేయబడింది."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIMను అన్లాక్ చేస్తోంది…"</string> diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml index c2441075d02d..605d0772ac6e 100644 --- a/packages/SystemUI/res-keyguard/values-th/strings.xml +++ b/packages/SystemUI/res-keyguard/values-th/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • กำลังชาร์จอย่างช้าๆ"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ปรับการชาร์จให้เหมาะสมเพื่อถนอมแบตเตอรี่"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ปัญหาเกี่ยวกับอุปกรณ์เสริมสำหรับการชาร์จ"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"กด \"เมนู\" เพื่อปลดล็อก"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"เครือข่ายถูกล็อก"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ไม่มี SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"โปรดใส่ SIM"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"ไม่มี SIM หรืออ่านไม่ได้ โปรดใส่ SIM"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ใช้งานไม่ได้"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"ปิดใช้งาน SIM อย่างถาวรแล้ว\n ติดต่อผู้ให้บริการไร้สายของคุณเพื่อรับ SIM ใหม่"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM ถูกล็อก"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM ถูกล็อกด้วย PUK"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"กำลังปลดล็อก SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml index cd8f81046f96..040ec9e9b703 100644 --- a/packages/SystemUI/res-keyguard/values-tl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mabagal na nagcha-charge"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Naka-optimize ang pag-charge para protektahan ang baterya"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Isyu sa pag-charge ng accessory"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pindutin ang Menu upang i-unlock."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Naka-lock ang network"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Walang SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Magdagdag ng SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Wala o hindi nababasa ang SIM. Magdagdag ng SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Hindi magagamit na SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"Permanenteng na-deactivate ang iyong SIM.\n Makipag-ugnayan sa iyong service provider ng wireless para sa isa pang SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"Naka-lock ang SIM."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"Naka-PUK lock ang SIM."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ina-unlock ang SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml index ddeba67719d7..750ba1174fe8 100644 --- a/packages/SystemUI/res-keyguard/values-tr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Yavaş şarj oluyor"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj işlemi pili korumak üzere optimize edildi"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarı ile ilgili sorun"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Kilidi açmak için Menü\'ye basın."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Ağ kilitli"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM yok"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM ekleyin."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM yok veya okunamıyor. SIM ekleyin."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kullanılamayan SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM\'iniz kalıcı olarak devre dışı bırakıldı.\n Başka bir SIM için kablosuz servis sağlayıcınızla iletişime geçin."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM kilitli."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM\'in PUK kilidi devrede."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM\'in kilidi açılıyor…"</string> diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml index f06d17df9146..169ea1f16eec 100644 --- a/packages/SystemUI/res-keyguard/values-uk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Повільне заряджання"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Заряджання оптимізовано, щоб захистити акумулятор"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблема із зарядним пристроєм"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Натисніть меню, щоб розблокувати."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мережу заблоковано"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Немає SIM-карти"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Додайте SIM-карту."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM-карта відсутня або недоступна для читання. Додайте SIM-карту."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непридатна SIM-карта."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM-карту деактивовано назавжди.\n Щоб отримати іншу, зверніться до свого постачальника послуг бездротового зв’язку."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM-карту заблоковано."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM-карту заблоковано PUK-кодом."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Розблокування SIM-карти…"</string> diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml index 8adbaca955f7..d7f7b653e92e 100644 --- a/packages/SystemUI/res-keyguard/values-ur/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آہستہ چارج ہو رہا ہے"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • بیٹری کی حفاظت کے لیے چارجنگ کو بہتر بنایا گیا"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • چارجنگ ایکسیسری کے ساتھ مسئلہ"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"غیر مقفل کرنے کیلئے مینیو دبائیں۔"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"نیٹ ورک مقفل ہو گیا"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"کوئی SIM نہیں ہے"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"ایک SIM شامل کریں۔"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM غائب ہے یا پڑھنے لائق نہیں ہے۔ ایک SIM شامل کریں۔"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ناقابل استعمال SIM۔"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"آپ کے SIM کو مستقل طور پر غیر فعال کر دیا گیا ہے۔\n کسی دوسرے SIM کیلئے اپنے وائرلیس سروس فراہم کنندہ سے رابطہ کریں۔"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM مقفل ہے۔"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"آپ کا SIM PUK مقفل ہے۔"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM کو غیر مقفل کیا جا رہا ہے…"</string> diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml index 96dfa05a74aa..40dbaf3142fa 100644 --- a/packages/SystemUI/res-keyguard/values-uz/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sekin quvvat olmoqda"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyani himoyalash uchun quvvatlash optimallashtirildi"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quvvatlash aksessuari bilan muammo"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Qulfdan chiqarish uchun Menyu tugmasini bosing."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tarmoq qulflangan"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM kartasiz"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"SIM karta qoʻshish."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM karta topilmadi yoki oʻqilmadi. SIM karta qoʻshish."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ishlamaydigan SIM."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM karta butunlay faolsizlantirildi.\n Boshqa SIM karta olish uchun simsiz aloqa operatoriga murojaat qiling."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM karta qulflandi."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM karta PUK kod bilan qulflangan."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"SIM karta qulfdan chiqarilmoqda…"</string> diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml index 41b5a33ba967..d5a33d37bc66 100644 --- a/packages/SystemUI/res-keyguard/values-vi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Đang sạc chậm"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quá trình sạc được tối ưu hoá để bảo vệ pin"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Có vấn đề với phụ kiện sạc"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Nhấn vào Menu để mở khóa."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mạng đã bị khóa"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Không có SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"Hãy thêm SIM."</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"Không tìm thấy hoặc không đọc được SIM. Hãy thêm SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM không sử dụng được."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM của bạn đã bị vô hiệu hoá vĩnh viễn.\n Hãy liên hệ với nhà cung cấp dịch vụ viễn thông không dây của bạn để yêu cầu cấp SIM khác."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM này đang bị khoá."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM này đang bị khoá PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Đang mở khoá SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml index 4c65832162ba..6de9ff944882 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 正在慢速充电"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 为保护电池,充电方式已优化"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充电配件有问题"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"按“菜单”即可解锁。"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"网络已锁定"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"没有 SIM 卡"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"请插入 SIM 卡。"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"SIM 卡缺失或无法读取。请插入 SIM 卡。"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡无法使用。"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"您的 SIM 卡已被永久停用。\n请与您的无线服务提供商联系,以便重新获取一张 SIM 卡。"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已被锁定。"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已用 PUK 码锁定。"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解锁 SIM 卡…"</string> diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml index dad6f31ac553..11966ca08dce 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 慢速充電中"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 為保護電池,系統已優化充電"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電配件發生問題"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"按下 [選單] 即可解鎖。"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"網絡已鎖定"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"請新增 SIM 卡。"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"找不到 SIM 卡或 SIM 卡無法讀取,請新增 SIM 卡。"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM 卡已永久停用。\n請向無線服務供應商索取其他 SIM 卡。"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已鎖定。"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已使用 PUK 鎖定。"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解鎖 SIM 卡…"</string> diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml index 88b7e4374ff9..e4f868a093d3 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 慢速充電中"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 為保護電池,充電效能已最佳化"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電配件有問題"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"按選單鍵解鎖。"</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"網路已鎖定"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"請新增 SIM 卡。"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"找不到 SIM 卡或 SIM 卡無法讀取,請新增 SIM 卡。"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"SIM 卡已永久停用。\n 請向無線服務供應商索取其他 SIM 卡。"</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"SIM 卡已鎖定。"</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"SIM 卡已使用 PUK 碼鎖定。"</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"正在解鎖 SIM 卡…"</string> diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml index c5e99abbda54..4fadc2e55ee9 100644 --- a/packages/SystemUI/res-keyguard/values-zu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml @@ -35,13 +35,9 @@ <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ishaja kancane"</string> <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ukushaja kuthuthukisiwe ukuze kuvikelwe ibhethri"</string> <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • • Inkinga ngesisekeli sokushaja"</string> - <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Chofoza Menyu ukuvula."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Inethiwekhi ivaliwe"</string> <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ayikho i-SIM"</string> - <string name="keyguard_missing_sim_instructions" msgid="7735360104844653246">"engeza i-SIM"</string> - <string name="keyguard_missing_sim_instructions_long" msgid="3451467338947610268">"I-SIM ayitholakali noma ayifundeki. engeza i-SIM"</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"I-SIM engasebenziseki."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5034635040020685428">"I-SIM yakho iyekiswe ukusebenza unomphela.\n Xhumana nomhlinzeki wakho wesevisi ngokungenazintambo ukuze uthole enye i-SIM."</string> <string name="keyguard_sim_locked_message" msgid="7095293254587575270">"I-SIM ikhiyiwe."</string> <string name="keyguard_sim_puk_locked_message" msgid="2503428315518592542">"I-SIM ikhiyiwe nge-PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="8489092646014631659">"Ivula i-SIM…"</string> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 28b58703b02d..565ed1085fb9 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -67,23 +67,13 @@ <!-- When the lock screen is showing and the phone plugged in with incompatible charger. --> <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string> - <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. --> - <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string> - <!-- SIM messages --><skip /> <!-- When the user inserts a sim card from an unsupported network, it becomes network locked --> <string name="keyguard_network_locked_message">Network locked</string> <!-- Shown when there is no SIM. --> <string name="keyguard_missing_sim_message_short">No SIM</string> - <!-- Shown to ask the user to add a SIM. --> - <string name="keyguard_missing_sim_instructions">Add a SIM.</string> - <!-- Shown to ask the user to add a SIM when sim is missing or not readable. --> - <string name="keyguard_missing_sim_instructions_long">The SIM is missing or not readable. Add a SIM.</string> <!-- Shown when SIM is permanently disabled. --> <string name="keyguard_permanent_disabled_sim_message_short">Unusable SIM.</string> - <!-- Shown to inform the user to SIM is permanently deactivated. --> - <string name="keyguard_permanent_disabled_sim_instructions">Your SIM has been permanently deactivated.\n - Contact your wireless service provider for another SIM.</string> <!-- Shown to tell the user that their SIM is locked and they must unlock it. --> <string name="keyguard_sim_locked_message">SIM is locked.</string> <!-- When the user enters a wrong sim pin too many times, it becomes PUK locked (Pin Unlock Kode) --> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 05f4334bbe89..74d435d18823 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -248,4 +248,14 @@ <!-- Tag set on the Compose implementation of the QS footer actions. --> <item type="id" name="tag_compose_qs_footer_actions" /> + + <!-- + Ids for the device entry icon. + device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg + device_entry_icon_fg: fp/lock/unlock icon + device_entry_icon_bg: background protection behind the icon + --> + <item type="id" name="device_entry_icon_view" /> + <item type="id" name="device_entry_icon_fg" /> + <item type="id" name="device_entry_icon_bg" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9e2ebf60fe8f..2e5bc47903e3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -629,7 +629,7 @@ <!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] --> <string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string> <!-- QuickSettings: Bluetooth dialog subtitle [CHAR LIMIT=NONE]--> - <string name="quick_settings_bluetooth_tile_subtitle">Tap a device to connect</string> + <string name="quick_settings_bluetooth_tile_subtitle">Tap to connect or disconnect a device </string> <!-- QuickSettings: Bluetooth dialog pair new devices [CHAR LIMIT=NONE]--> <string name="pair_new_bluetooth_devices">Pair new device</string> <!-- QuickSettings: Bluetooth dialog see all devices [CHAR LIMIT=NONE]--> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index b186018ba78a..375727437b8b 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -318,9 +318,8 @@ class ActiveUnlockConfig @Inject constructor( keyguardUpdateMonitor?.let { val anyFaceEnrolled = it.isFaceEnrolled - val anyFingerprintEnrolled = - it.getCachedIsUnlockWithFingerprintPossible( - selectedUserInteractor.getSelectedUserId()) + val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible( + selectedUserInteractor.getSelectedUserId()) val udfpsEnrolled = it.isUdfpsEnrolled if (!anyFaceEnrolled && !anyFingerprintEnrolled) { @@ -374,9 +373,8 @@ class ActiveUnlockConfig @Inject constructor( pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" + "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") pw.println(" faceEnrolled=${it.isFaceEnrolled}") - pw.println(" fpEnrolled=${ - it.getCachedIsUnlockWithFingerprintPossible( - selectedUserInteractor.getSelectedUserId())}") + pw.println(" fpUnlockPossible=${ + it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}") pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") } ?: pw.println(" keyguardUpdateMonitor is uninitialized") } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index 873c3d9105db..1cfa816f4612 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -39,10 +39,10 @@ import androidx.annotation.VisibleForTesting; import com.android.keyguard.logging.CarrierTextManagerLogger; import com.android.settingslib.WirelessUtils; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.res.R; import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository; import com.android.systemui.telephony.TelephonyListenerManager; @@ -612,36 +612,6 @@ public class CarrierTextManager { return list; } - private CharSequence getCarrierHelpTextForSimState(int simState, - String plmn, String spn) { - int carrierHelpTextId = 0; - CarrierTextManager.StatusMode status = getStatusForIccState(simState); - switch (status) { - case NetworkLocked: - carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; - break; - - case SimMissing: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; - break; - - case SimPermDisabled: - carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; - break; - - case SimMissingLocked: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions; - break; - - case Normal: - case SimLocked: - case SimPukLocked: - break; - } - - return mContext.getText(carrierHelpTextId); - } - /** Injectable Buildeer for {@#link CarrierTextManager}. */ public static class Builder { private final Context mContext; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 2e212552caaa..3d8aaaf6f13f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -65,15 +65,23 @@ public class KeyguardPasswordViewController private boolean mPaused; private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> { - // Check if this was the result of hitting the enter key + // Check if this was the result of hitting the IME done action final boolean isSoftImeEvent = event == null && (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT); - final boolean isKeyboardEnterKey = event != null - && KeyEvent.isConfirmKey(event.getKeyCode()) - && event.getAction() == KeyEvent.ACTION_DOWN; - if (isSoftImeEvent || isKeyboardEnterKey) { + if (isSoftImeEvent) { + verifyPasswordAndUnlock(); + return true; + } + return false; + }; + + private final View.OnKeyListener mKeyListener = (v, keyCode, keyEvent) -> { + final boolean isKeyboardEnterKey = keyEvent != null + && KeyEvent.isConfirmKey(keyCode) + && keyEvent.getAction() == KeyEvent.ACTION_UP; + if (isKeyboardEnterKey) { verifyPasswordAndUnlock(); return true; } @@ -140,15 +148,16 @@ public class KeyguardPasswordViewController | InputType.TYPE_TEXT_VARIATION_PASSWORD); mView.onDevicePostureChanged(mPostureController.getDevicePosture()); + mPostureController.addCallback(mPostureCallback); // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); + mPasswordEntry.setOnKeyListener(mKeyListener); mPasswordEntry.addTextChangedListener(mTextWatcher); // Poke the wakelock any time the text is selected or modified mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity()); - mSwitchImeButton.setOnClickListener(v -> { mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer // Do not show auxiliary subtypes in password lock screen. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 681aa70402bd..175544d48c38 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -89,10 +89,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { - mOkButton.performClick(); - return true; - } else if (keyCode == KeyEvent.KEYCODE_DEL) { + if (keyCode == KeyEvent.KEYCODE_DEL) { mDeleteButton.performClick(); return true; } @@ -110,6 +107,15 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (KeyEvent.isConfirmKey(keyCode)) { + mOkButton.performClick(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override protected int getPromptReasonStringRes(int reason) { switch (reason) { case PROMPT_REASON_RESTART: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index b7d1171431a8..376933dc5519 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -41,6 +41,9 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB if (event.getAction() == KeyEvent.ACTION_DOWN) { return mView.onKeyDown(keyCode, event); } + if (event.getAction() == KeyEvent.ACTION_UP) { + return mView.onKeyUp(keyCode, event); + } return false; }; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 7101ed599b86..1b6112f52082 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -755,7 +755,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mView.onResume( mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()), - mKeyguardStateController.isFaceAuthEnabled()); + mKeyguardStateController.isFaceEnrolled()); } /** Sets an initial message that would override the default message */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 13f9d3e1038e..05fb5fa75e9e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -246,7 +246,7 @@ public class KeyguardSimPukViewController private boolean checkPuk() { // make sure the puk is at least 8 digits long. - if (mPasswordEntry.getText().length() == 8) { + if (mPasswordEntry.getText().length() >= 8) { mPukText = mPasswordEntry.getText(); return true; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 7d6240b0dc2c..f19a9ed5546f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -441,7 +441,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mFaceRunningState = BIOMETRIC_STATE_STOPPED; private boolean mIsDreaming; private boolean mLogoutEnabled; - private boolean mIsFaceEnrolled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private int mPostureState = DEVICE_POSTURE_UNKNOWN; private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; @@ -2083,7 +2082,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mFingerprintLockedOut; private boolean mFingerprintLockedOutPermanent; private boolean mFaceLockedOutPermanent; - private final HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>(); /** * When we receive a {@link android.content.Intent#ACTION_SIM_STATE_CHANGED} broadcast, @@ -2701,16 +2699,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void updateFaceEnrolled(int userId) { - final Boolean isFaceEnrolled = isFaceSupported() - && mBiometricEnabledForUser.get(userId) - && mAuthController.isFaceAuthEnrolled(userId); - if (mIsFaceEnrolled != isFaceEnrolled) { - mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled); - } - mIsFaceEnrolled = isFaceEnrolled; - } - private boolean isFaceSupported() { return mFaceManager != null && !mFaceSensorProperties.isEmpty(); } @@ -2750,10 +2738,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * @return true if there's at least one face enrolled for the given user. + */ + public boolean isFaceEnrolled(int userId) { + return mAuthController.isFaceAuthEnrolled(userId); + } + + /** * @return true if there's at least one face enrolled */ public boolean isFaceEnrolled() { - return mIsFaceEnrolled; + return isFaceEnrolled(mSelectedUserInteractor.getSelectedUserId()); } private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @@ -3442,49 +3437,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } @SuppressLint("MissingPermission") - @VisibleForTesting - boolean isUnlockWithFingerprintPossible(int userId) { - // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. - boolean newFpEnrolled = isFingerprintSupported() - && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId); - Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); - if (oldFpEnrolled != newFpEnrolled) { - mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled); - } - mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled); - return mIsUnlockWithFingerprintPossible.get(userId); - } - - /** - * Cached value for whether fingerprint is enrolled and possible to use for authentication. - * Note: checking fingerprint enrollment directly with the AuthController requires an IPC. - */ - public boolean getCachedIsUnlockWithFingerprintPossible(int userId) { - return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); + public boolean isUnlockWithFingerprintPossible(int userId) { + return isFingerprintSupported() + && !isFingerprintDisabled(userId) && mAuthController.isFingerprintEnrolled(userId); } /** * @deprecated This is being migrated to use modern architecture. */ + @VisibleForTesting @Deprecated - private boolean isUnlockWithFacePossible(int userId) { + public boolean isUnlockWithFacePossible(int userId) { if (isFaceAuthInteractorEnabled()) { return getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled(); } - return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId); - } - - /** - * If face hardware is available, user has enrolled and enabled auth via setting. - * - * @deprecated This is being migrated to use modern architecture. - */ - @Deprecated - public boolean isFaceAuthEnabledForUser(int userId) { - // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. - updateFaceEnrolled(userId); - return mIsFaceEnrolled; + return isFaceSupported() && isFaceEnrolled(userId) && !isFaceDisabled(userId); } private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) { diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index fa07072b7fe1..5bf8d635f8ee 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -660,19 +660,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } - fun logFpEnrolledUpdated(userId: Int, oldValue: Boolean, newValue: Boolean) { - logBuffer.log( - TAG, - DEBUG, - { - int1 = userId - bool1 = oldValue - bool2 = newValue - }, - { "Fp enrolled state changed for userId: $int1 old: $bool1, new: $bool2" } - ) - } - fun logTrustUsuallyManagedUpdated( userId: Int, oldValue: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 105de16ce66b..cd8bef1ab6ed 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -38,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.systemui.Flags; import java.util.HashMap; @@ -91,16 +92,48 @@ class MenuAnimationController { } void moveToPosition(PointF position) { - moveToPositionX(position.x); - moveToPositionY(position.y); + moveToPosition(position, /* animateMovement = */ false); + } + + /* Moves position without updating underlying percentage position. Can be animated. */ + void moveToPosition(PointF position, boolean animateMovement) { + if (Flags.floatingMenuImeDisplacementAnimation()) { + moveToPositionX(position.x, animateMovement); + moveToPositionY(position.y, animateMovement); + } else { + moveToPositionX(position.x, /* animateMovement = */ false); + moveToPositionY(position.y, /* animateMovement = */ false); + } } void moveToPositionX(float positionX) { - DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); + moveToPositionX(positionX, /* animateMovement = */ false); } - private void moveToPositionY(float positionY) { - DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); + void moveToPositionX(float positionX, boolean animateMovement) { + if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + springMenuWith(DynamicAnimation.TRANSLATION_X, + createSpringForce(), + /* velocity = */ 0, + positionX, /* writeToPosition = */ false); + } else { + DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); + } + } + + void moveToPositionY(float positionY) { + moveToPositionY(positionY, /* animateMovement = */ false); + } + + void moveToPositionY(float positionY, boolean animateMovement) { + if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + springMenuWith(DynamicAnimation.TRANSLATION_Y, + createSpringForce(), + /* velocity = */ 0, + positionY, /* writeToPosition = */ false); + } else { + DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); + } } void moveToPositionYIfNeeded(float positionY) { @@ -151,7 +184,7 @@ class MenuAnimationController { void moveAndPersistPosition(PointF position) { moveToPosition(position); mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); - constrainPositionAndUpdate(position); + constrainPositionAndUpdate(position, /* writeToPosition = */ true); } void removeMenu() { @@ -180,17 +213,13 @@ class MenuAnimationController { flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, startXVelocity, FLING_FRICTION_SCALAR, - new SpringForce() - .setStiffness(SPRING_STIFFNESS) - .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + createSpringForce(), finalPositionX); flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y, velocityY, FLING_FRICTION_SCALAR, - new SpringForce() - .setStiffness(SPRING_STIFFNESS) - .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + createSpringForce(), /* finalPosition= */ null); } @@ -226,7 +255,8 @@ class MenuAnimationController { final float endPosition = finalPosition != null ? finalPosition : Math.max(min, Math.min(max, endValue)); - springMenuWith(property, spring, endVelocity, endPosition); + springMenuWith(property, spring, endVelocity, endPosition, + /* writeToPosition = */ true); }); cancelAnimation(property); @@ -242,7 +272,7 @@ class MenuAnimationController { @VisibleForTesting void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring, - float velocity, float finalPosition) { + float velocity, float finalPosition, boolean writeToPosition) { final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property); final SpringAnimation springAnimation = new SpringAnimation(mMenuView, menuPositionProperty) @@ -257,7 +287,7 @@ class MenuAnimationController { DynamicAnimation::isRunning); if (!areAnimationsRunning) { onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(), - mMenuView.getTranslationY())); + mMenuView.getTranslationY()), writeToPosition); } }) .setStartVelocity(velocity); @@ -281,7 +311,8 @@ class MenuAnimationController { if (currentXTranslation < draggableBounds.left || currentXTranslation > draggableBounds.right) { constrainPositionAndUpdate( - new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY())); + new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()), + /* writeToPosition = */ true); moveToEdgeAndHide(); return true; } @@ -298,15 +329,19 @@ class MenuAnimationController { return mMenuView.isMoveToTucked(); } - void moveToEdgeAndHide() { - mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); - + PointF getTuckedMenuPosition() { final PointF position = mMenuView.getMenuPosition(); final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f; final float endX = isOnLeftSide() ? position.x - menuHalfWidth : position.x + menuHalfWidth; - moveToPosition(new PointF(endX, position.y)); + return new PointF(endX, position.y); + } + + void moveToEdgeAndHide() { + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); + final PointF position = mMenuView.getMenuPosition(); + moveToPosition(getTuckedMenuPosition()); // Keep the touch region let users could click extra space to pop up the menu view // from the screen edge @@ -335,6 +370,11 @@ class MenuAnimationController { mPositionAnimations.get(property).cancel(); } + @VisibleForTesting + DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) { + return mPositionAnimations.getOrDefault(property, null); + } + void onDraggingStart() { mMenuView.onDraggingStart(); } @@ -361,9 +401,9 @@ class MenuAnimationController { .start(); } - private void onSpringAnimationsEnd(PointF position) { + private void onSpringAnimationsEnd(PointF position, boolean writeToPosition) { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); - constrainPositionAndUpdate(position); + constrainPositionAndUpdate(position, writeToPosition); fadeOutIfEnabled(); @@ -372,7 +412,7 @@ class MenuAnimationController { } } - private void constrainPositionAndUpdate(PointF position) { + private void constrainPositionAndUpdate(PointF position, boolean writeToPosition) { final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme(); // Have the space gap margin between the top bound and the menu view, so actually the // position y range needs to cut the margin. @@ -384,7 +424,12 @@ class MenuAnimationController { final float percentageY = position.y < 0 || draggableBounds.height() == 0 ? MIN_PERCENT : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); - mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + + if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) { + mMenuView.onEdgeChangedIfNeeded(); + } else { + mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + } } void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) { @@ -463,4 +508,11 @@ class MenuAnimationController { mProperty.setValue(menuView, value); } } + + @VisibleForTesting + static SpringForce createSpringForce() { + return new SpringForce() + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO); + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index e1612b071458..ea5a56c6a0f5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -54,7 +54,6 @@ class MenuView extends FrameLayout implements private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>(); private final AccessibilityTargetAdapter mAdapter; private final MenuViewModel mMenuViewModel; - private final MenuAnimationController mMenuAnimationController; private final Rect mBoundsInParent = new Rect(); private final RecyclerView mTargetFeaturesView; private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = @@ -70,6 +69,7 @@ class MenuView extends FrameLayout implements private boolean mIsMoveToTucked; + private final MenuAnimationController mMenuAnimationController; private OnTargetFeaturesChangeListener mFeaturesChangeListener; MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { @@ -197,8 +197,30 @@ class MenuView extends FrameLayout implements } void onPositionChanged() { - final PointF position = mMenuViewAppearance.getMenuPosition(); - mMenuAnimationController.moveToPosition(position); + onPositionChanged(/* animateMovement = */ false); + } + + void onPositionChanged(boolean animateMovement) { + final PointF position; + if (isMoveToTucked()) { + position = mMenuAnimationController.getTuckedMenuPosition(); + } else { + position = getMenuPosition(); + } + + // We can skip animating if FAB is not visible + if (Flags.floatingMenuImeDisplacementAnimation() + && animateMovement && getVisibility() == VISIBLE) { + mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true); + // onArrivalAtPosition() is called at the end of the animation. + } else { + mMenuAnimationController.moveToPosition(position); + onArrivalAtPosition(); // no animation, so we call this immediately. + } + } + + void onArrivalAtPosition() { + final PointF position = getMenuPosition(); onBoundsInParentChanged((int) position.x, (int) position.y); if (isMoveToTucked()) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index b6e8997c31e7..fbca02290236 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -59,6 +59,7 @@ import androidx.lifecycle.Observer; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.bubbles.DismissViewUtils; @@ -331,7 +332,7 @@ class MenuViewLayer extends FrameLayout implements mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop); mMenuView.onEdgeChanged(); - mMenuView.onPositionChanged(); + mMenuView.onPositionChanged(/* animateMovement = */ true); mImeInsetsRect.set(imeInsetsRect); } @@ -362,6 +363,10 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } + + if (Flags.floatingMenuImeDisplacementAnimation()) { + mMenuView.onArrivalAtPosition(); + } } private CharSequence getMigrationMessage() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 272e0f21e74a..934f9f919d5d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,7 +46,6 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager @@ -240,15 +239,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } REASON_AUTH_KEYGUARD -> { if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) { - udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds) - UdfpsKeyguardViewController( - view.addUdfpsView(R.layout.udfps_keyguard_view), - statusBarStateController, - primaryBouncerInteractor, - dialogManager, - dumpManager, - alternateBouncerInteractor, - udfpsKeyguardViewModels.get(), + // note: empty controller, currently shows no visual affordance + // instead SysUI will show the fingerprint icon in its DeviceEntryIconView + UdfpsBpViewController( + view.addUdfpsView(R.layout.udfps_bp_view), + statusBarStateController, + primaryBouncerInteractor, + dialogManager, + dumpManager ) } else { UdfpsKeyguardViewControllerLegacy( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index 56e3f3995d1c..2f493ac1dccf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -2,11 +2,11 @@ package com.android.systemui.biometrics.ui import android.content.Context import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.graphics.Insets import android.text.TextUtils import android.util.AttributeSet import android.view.View import android.view.WindowInsets -import android.view.WindowInsets.Type.ime import android.view.accessibility.AccessibilityManager import android.widget.LinearLayout import android.widget.TextView @@ -41,7 +41,10 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : } override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { - val imeBottomInset = insets.getInsets(ime()).bottom + val statusBarInsets: Insets = insets.getInsets(WindowInsets.Type.statusBars()) + val keyboardInsets: Insets = insets.getInsets(WindowInsets.Type.ime()) + val navigationInsets: Insets = insets.getInsets(WindowInsets.Type.navigationBars()) + val imeBottomInset = keyboardInsets.bottom if (bottomInset != imeBottomInset) { val titleView: TextView? = findViewById(R.id.title) if (titleView != null) { @@ -61,8 +64,14 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : } } } - setPadding(paddingLeft, paddingTop, paddingRight, imeBottomInset) - return insets.inset(0, 0, 0, imeBottomInset) + + setPadding( + 0, + statusBarInsets.top, + 0, + if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom + ) + return WindowInsets.CONSUMED } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt index 75331f083851..10868970fcbb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt @@ -1,7 +1,11 @@ package com.android.systemui.biometrics.ui import android.content.Context +import android.graphics.Insets import android.util.AttributeSet +import android.view.View +import android.view.WindowInsets +import android.view.WindowInsets.Type import android.widget.LinearLayout import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.ui.binder.CredentialViewBinder @@ -9,7 +13,7 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel /** Pattern credential view for BiometricPrompt. */ class CredentialPatternView(context: Context, attrs: AttributeSet?) : - LinearLayout(context, attrs), CredentialView { + LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener { /** Initializes the view. */ override fun init( @@ -20,4 +24,17 @@ class CredentialPatternView(context: Context, attrs: AttributeSet?) : ) { CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel) } + + override fun onFinishInflate() { + super.onFinishInflate() + setOnApplyWindowInsetsListener(this) + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + val statusBarInsets: Insets = insets.getInsets(Type.statusBars()) + val navigationInsets: Insets = insets.getInsets(Type.navigationBars()) + + setPadding(0, statusBarInsets.top, 0, navigationInsets.bottom) + return WindowInsets.CONSUMED + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 21578f491de7..56dfa5ed337c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -415,7 +415,7 @@ constructor( /** Whether we want to wait to show the bouncer in case passive auth succeeds. */ private fun usePrimaryBouncerPassiveAuthDelay(): Boolean { val canRunFaceAuth = - keyguardStateController.isFaceAuthEnabled && + keyguardStateController.isFaceEnrolled && keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) && keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth() val canRunActiveUnlock = diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt index 323070a84863..07814512b4b8 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -22,6 +22,7 @@ import android.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import com.android.systemui.shade.TouchLogger import kotlin.math.pow import kotlin.math.sqrt @@ -36,11 +37,18 @@ import kotlinx.coroutines.DisposableHandle class LongPressHandlingView( context: Context, attrs: AttributeSet?, + private val longPressDuration: () -> Long, ) : View( context, attrs, ) { + + constructor( + context: Context, + attrs: AttributeSet?, + ) : this(context, attrs, { ViewConfiguration.getLongPressTimeout().toLong() }) + interface Listener { /** Notifies that a long-press has been detected by the given view. */ fun onLongPressDetected( @@ -77,6 +85,7 @@ class LongPressHandlingView( ) }, onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) }, + longPressDuration = longPressDuration, ) } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt index c2d4d12d03c3..a742e8d614b1 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -33,6 +33,8 @@ class LongPressHandlingViewInteractionHandler( private val onLongPressDetected: (x: Int, y: Int) -> Unit, /** Callback reporting the a single tap gesture was detected at the given coordinates. */ private val onSingleTapDetected: () -> Unit, + /** Time for the touch to be considered a long-press in ms */ + private val longPressDuration: () -> Long, ) { sealed class MotionEventModel { object Other : MotionEventModel() @@ -77,7 +79,7 @@ class LongPressHandlingViewInteractionHandler( cancelScheduledLongPress() if ( event.distanceMoved <= ViewConfiguration.getTouchSlop() && - event.gestureDuration < ViewConfiguration.getLongPressTimeout() + event.gestureDuration < longPressDuration() ) { dispatchSingleTap() } @@ -103,7 +105,7 @@ class LongPressHandlingViewInteractionHandler( y = y, ) }, - ViewConfiguration.getLongPressTimeout().toLong(), + longPressDuration(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 9859651d3405..06ec17ff80d1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -113,12 +113,12 @@ object Flags { // TODO(b/292213543): Tracking Bug @JvmField val NOTIFICATION_GROUP_EXPANSION_CHANGE = - unreleasedFlag("notification_group_expansion_change", teamfood = true) + releasedFlag("notification_group_expansion_change") // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = - unreleasedFlag("notification_lockscreen_mgr_bg_thread") + unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true) // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 119ade48d4f7..c56dfde86573 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -48,6 +48,7 @@ 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 dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -72,7 +73,7 @@ constructor( private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val context: Context, private val keyguardIndicationController: KeyguardIndicationController, - private val lockIconViewController: LockIconViewController, + private val lockIconViewController: Lazy<LockIconViewController>, private val shadeInteractor: ShadeInteractor, private val interactionJankMonitor: InteractionJankMonitor, private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, @@ -131,7 +132,9 @@ constructor( val indicationArea = KeyguardIndicationArea(context, null) keyguardIndicationController.setIndicationArea(indicationArea) - lockIconViewController.setLockIconView(LockIconView(context, null)) + if (!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + lockIconViewController.get().setLockIconView(LockIconView(context, null)) + } } private fun bindKeyguardRootView() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 2dc490886c3d..4d26466b0924 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -93,6 +93,12 @@ interface KeyguardRepository { val isKeyguardOccluded: Flow<Boolean> /** + * Whether the device is locked or unlocked right now. This is true when keyguard has been + * dismissed or can be dismissed by a swipe + */ + val isKeyguardUnlocked: StateFlow<Boolean> + + /** * Observable for the signal that keyguard is about to go away. * * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed. @@ -332,6 +338,44 @@ constructor( } .distinctUntilChanged() + override val isKeyguardUnlocked: StateFlow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "updated isKeyguardUnlocked due to onUnlockedChanged" + ) + } + + override fun onKeyguardShowingChanged() { + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "updated isKeyguardUnlocked due to onKeyguardShowingChanged" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "initial isKeyguardUnlocked" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + .distinctUntilChanged() + .stateIn( + scope, + SharingStarted.Eagerly, + initialValue = false, + ) + override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { 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 b953b4879a4a..eaec0d4febb7 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 @@ -30,7 +30,6 @@ import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -78,7 +77,6 @@ constructor( private val powerInteractor: PowerInteractor, featureFlags: FeatureFlags, sceneContainerFlags: SceneContainerFlags, - deviceEntryRepository: DeviceEntryRepository, bouncerRepository: KeyguardBouncerRepository, configurationRepository: ConfigurationRepository, shadeRepository: ShadeRepository, @@ -160,7 +158,7 @@ constructor( val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing /** Whether the keyguard is unlocked or not. */ - val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked + val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index 122c4c44cf9f..55b420b06413 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -29,8 +29,10 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi /** Handles key events arriving when the keyguard is showing or device is dozing. */ +@ExperimentalCoroutinesApi @SysUISingleton class KeyguardKeyEventInteractor @Inject @@ -53,9 +55,14 @@ constructor( } if (event.handleAction()) { + if (KeyEvent.isConfirmKey(event.keyCode)) { + if (isDeviceAwake()) { + return collapseShadeLockedOrShowPrimaryBouncer() + } + } + when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -91,16 +98,22 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - shadeController.animateCollapseShadeForced() - return true + return collapseShadeLockedOrShowPrimaryBouncer() } return false } - private fun dispatchSpaceEvent(): Boolean { - if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) { - shadeController.animateCollapseShadeForced() - return true + private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { + when (statusBarStateController.state) { + StatusBarState.SHADE -> return false + StatusBarState.SHADE_LOCKED -> { + shadeController.animateCollapseShadeForced() + return true + } + StatusBarState.KEYGUARD -> { + statusBarKeyguardViewManager.showPrimaryBouncer(true) + return true + } } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt new file mode 100644 index 000000000000..e82ea7fecd05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -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.systemui.keyguard.ui.binder + +import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.FalsingManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object DeviceEntryIconViewBinder { + + /** + * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its + * background. + */ + @SuppressLint("ClickableViewAccessibility") + @JvmStatic + fun bind( + view: DeviceEntryIconView, + viewModel: DeviceEntryIconViewModel, + falsingManager: FalsingManager, + ) { + val iconView = view.iconView + val bgView = view.bgView + val longPressHandlingView = view.longPressHandlingView + longPressHandlingView.listener = + object : LongPressHandlingView.Listener { + override fun onLongPressDetected(view: View, x: Int, y: Int) { + if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + return + } + viewModel.onLongPress() + } + } + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.iconViewModel.collect { iconViewModel -> + iconView.setImageState( + view.getIconState(iconViewModel.type, iconViewModel.useAodVariant), + /* merge */ false + ) + iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint) + iconView.alpha = iconViewModel.alpha + iconView.setPadding( + iconViewModel.padding, + iconViewModel.padding, + iconViewModel.padding, + iconViewModel.padding, + ) + } + } + launch { + viewModel.backgroundViewModel.collect { bgViewModel -> + bgView.alpha = bgViewModel.alpha + bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + } + } + launch { + viewModel.burnInViewModel.collect { burnInViewModel -> + view.translationX = burnInViewModel.x.toFloat() + view.translationY = burnInViewModel.y.toFloat() + view.aodFpDrawable.progress = burnInViewModel.progress + } + } + launch { + viewModel.isLongPressEnabled.collect { isEnabled -> + longPressHandlingView.setLongPressHandlingEnabled(isEnabled) + } + } + launch { + viewModel.accessibilityDelegateHint.collect { hint -> + view.accessibilityHintType = hint + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt new file mode 100644 index 000000000000..c9e395402dad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -0,0 +1,272 @@ +/* + * 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.content.Context +import android.graphics.drawable.AnimatedStateListDrawable +import android.graphics.drawable.AnimatedVectorDrawable +import android.util.AttributeSet +import android.util.StateSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable +import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.res.R + +class DeviceEntryIconView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttrs: Int = 0, +) : FrameLayout(context, attrs, defStyleAttrs) { + val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } + val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } + val aodFpDrawable: LottieDrawable = LottieDrawable() + var accessibilityHintType: AccessibilityHintType = AccessibilityHintType.NONE + + private var animatedIconDrawable: AnimatedStateListDrawable = AnimatedStateListDrawable() + + init { + setupIconStates() + setupIconTransitions() + setupAccessibilityDelegate() + + // Ordering matters. From background to foreground we want: + // bgView, iconView, longpressHandlingView overlay + addBgImageView() + addIconImageView() + addLongpressHandlingView() + } + + private fun setupAccessibilityDelegate() { + accessibilityDelegate = + object : AccessibilityDelegate() { + private val accessibilityAuthenticateHint = + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + resources.getString(R.string.accessibility_authenticate_hint) + ) + private val accessibilityEnterHint = + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + resources.getString(R.string.accessibility_enter_hint) + ) + override fun onInitializeAccessibilityNodeInfo( + v: View, + info: AccessibilityNodeInfo + ) { + super.onInitializeAccessibilityNodeInfo(v, info) + when (accessibilityHintType) { + AccessibilityHintType.AUTHENTICATE -> + info.addAction(accessibilityAuthenticateHint) + AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint) + AccessibilityHintType.NONE -> return + } + } + } + } + + private fun setupIconStates() { + // Lockscreen States + // LOCK + animatedIconDrawable.addState( + getIconState(IconType.LOCK, false), + context.getDrawable(R.drawable.ic_lock)!!, + R.id.locked, + ) + // UNLOCK + animatedIconDrawable.addState( + getIconState(IconType.UNLOCK, false), + context.getDrawable(R.drawable.ic_unlocked)!!, + R.id.unlocked, + ) + // FINGERPRINT + animatedIconDrawable.addState( + getIconState(IconType.FINGERPRINT, false), + context.getDrawable(R.drawable.ic_kg_fingerprint)!!, + R.id.locked_fp, + ) + + // AOD states + // LOCK + animatedIconDrawable.addState( + getIconState(IconType.LOCK, true), + context.getDrawable(R.drawable.ic_lock_aod)!!, + R.id.locked_aod, + ) + // UNLOCK + animatedIconDrawable.addState( + getIconState(IconType.UNLOCK, true), + context.getDrawable(R.drawable.ic_unlocked_aod)!!, + R.id.unlocked_aod, + ) + // FINGERPRINT + LottieCompositionFactory.fromRawRes(mContext, R.raw.udfps_aod_fp).addListener { result -> + aodFpDrawable.setComposition(result) + } + animatedIconDrawable.addState( + getIconState(IconType.FINGERPRINT, true), + aodFpDrawable, + R.id.udfps_aod_fp, + ) + + // WILDCARD: should always be the last state added since any states will match with this + // and therefore won't get matched with subsequent states. + animatedIconDrawable.addState( + StateSet.WILD_CARD, + context.getDrawable(R.color.transparent)!!, + R.id.no_icon, + ) + } + + private fun setupIconTransitions() { + // LockscreenFp <=> LockscreenUnlocked + animatedIconDrawable.addTransition( + R.id.locked_fp, + R.id.unlocked, + context.getDrawable(R.drawable.fp_to_unlock) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked_fp, + context.getDrawable(R.drawable.unlock_to_fp) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenLocked <=> AodLocked + animatedIconDrawable.addTransition( + R.id.locked_aod, + R.id.locked, + context.getDrawable(R.drawable.lock_aod_to_ls) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.locked, + R.id.locked_aod, + context.getDrawable(R.drawable.lock_ls_to_aod) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenUnlocked <=> AodUnlocked + animatedIconDrawable.addTransition( + R.id.unlocked_aod, + R.id.unlocked, + context.getDrawable(R.drawable.unlocked_aod_to_ls) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.unlocked_aod, + context.getDrawable(R.drawable.unlocked_ls_to_aod) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenLocked <=> LockscreenUnlocked + animatedIconDrawable.addTransition( + R.id.locked, + R.id.unlocked, + context.getDrawable(R.drawable.lock_to_unlock) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked, + context.getDrawable(R.drawable.unlocked_to_locked) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenFingerprint <=> LockscreenLocked + animatedIconDrawable.addTransition( + R.id.locked_fp, + R.id.locked, + context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable, + /* reversible */ true, + ) + + // LockscreenUnlocked <=> AodLocked + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked_aod, + context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable, + /* reversible */ true, + ) + } + + private fun addLongpressHandlingView() { + addView(longPressHandlingView) + val lp = longPressHandlingView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + longPressHandlingView.setLayoutParams(lp) + } + + private fun addIconImageView() { + iconView.scaleType = ImageView.ScaleType.CENTER_CROP + iconView.setImageDrawable(animatedIconDrawable) + addView(iconView) + val lp = iconView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + lp.gravity = Gravity.CENTER + iconView.setLayoutParams(lp) + } + + private fun addBgImageView() { + bgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg)) + addView(bgView) + val lp = bgView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + bgView.setLayoutParams(lp) + } + + fun getIconState(icon: IconType, aod: Boolean): IntArray { + val lockIconState = IntArray(2) + when (icon) { + IconType.LOCK -> lockIconState[0] = android.R.attr.state_first + IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last + IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle + } + if (aod) { + lockIconState[1] = android.R.attr.state_single + } else { + lockIconState[1] = -android.R.attr.state_single + } + return lockIconState + } + + enum class IconType { + LOCK, + UNLOCK, + FINGERPRINT, + } + + enum class AccessibilityHintType { + NONE, + AUTHENTICATE, + ENTER, + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index d8e43966990e..21eba56dcd83 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -23,8 +23,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection @@ -44,7 +44,7 @@ class DefaultKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultLockIconSection: DefaultLockIconSection, + defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, @@ -61,7 +61,7 @@ constructor( override val sections = setOf( defaultIndicationAreaSection, - defaultLockIconSection, + defaultDeviceEntryIconSection, defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index ce76f56fad2f..f8dd7c1a58c7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -23,8 +23,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdf import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection @@ -38,7 +38,7 @@ class ShortcutsBesideUdfpsKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultLockIconSection: DefaultLockIconSection, + defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, @@ -54,7 +54,7 @@ constructor( override val sections = setOf( defaultIndicationAreaSection, - defaultLockIconSection, + defaultDeviceEntryIconSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index f4bc7137b50c..62c5988ff42c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -33,11 +33,18 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView +import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi -class DefaultLockIconSection +@ExperimentalCoroutinesApi +class DefaultDeviceEntryIconSection @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, @@ -46,24 +53,47 @@ constructor( private val context: Context, private val notificationPanelView: NotificationPanelView, private val featureFlags: FeatureFlags, - private val lockIconViewController: LockIconViewController, + private val lockIconViewController: Lazy<LockIconViewController>, + private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, + private val falsingManager: Lazy<FalsingManager>, ) : KeyguardSection() { - private val lockIconViewId = R.id.lock_icon_view + private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if ( + !featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) && + !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) + ) { return } - notificationPanelView.findViewById<View>(lockIconViewId).let { + + notificationPanelView.findViewById<View>(R.id.lock_icon_view).let { notificationPanelView.removeView(it) } - val view = LockIconView(context, null).apply { id = lockIconViewId } + + val view = + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } + } else { + // Flags.MIGRATE_LOCK_ICON + LockIconView(context, null).apply { id = deviceEntryIconViewId } + } constraintLayout.addView(view) } override fun bindData(constraintLayout: ConstraintLayout) { - constraintLayout.findViewById<LockIconView?>(lockIconViewId)?.let { - lockIconViewController.setLockIconView(it) + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { + DeviceEntryIconViewBinder.bind( + it, + deviceEntryIconViewModel.get(), + falsingManager.get(), + ) + } + } else { + constraintLayout.findViewById<LockIconView?>(deviceEntryIconViewId)?.let { + lockIconViewController.get().setLockIconView(it) + } } } @@ -84,30 +114,30 @@ constructor( val defaultDensity = DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / DisplayMetrics.DENSITY_DEFAULT.toFloat() - val lockIconRadiusPx = (defaultDensity * 36).toInt() + val iconRadiusPx = (defaultDensity * 36).toInt() if (isUdfpsSupported) { authController.udfpsLocation?.let { udfpsLocation -> - centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet) + centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) } } else { - centerLockIcon( + centerIcon( Point( (widthPixels / 2).toInt(), - (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt() ), - lockIconRadiusPx * scaleFactor, + iconRadiusPx * scaleFactor, constraintSet, ) } } override fun removeViews(constraintLayout: ConstraintLayout) { - constraintLayout.removeView(lockIconViewId) + constraintLayout.removeView(deviceEntryIconViewId) } @VisibleForTesting - internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { + internal fun centerIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { val sensorRect = Rect().apply { set( @@ -119,17 +149,17 @@ constructor( } constraintSet.apply { - constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left) - constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top) + constrainWidth(deviceEntryIconViewId, sensorRect.right - sensorRect.left) + constrainHeight(deviceEntryIconViewId, sensorRect.bottom - sensorRect.top) connect( - lockIconViewId, + deviceEntryIconViewId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, sensorRect.top ) connect( - lockIconViewId, + deviceEntryIconViewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt new file mode 100644 index 000000000000..842dde352c71 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -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.systemui.keyguard.ui.viewmodel + +import android.graphics.Color +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +@ExperimentalCoroutinesApi +class DeviceEntryIconViewModel @Inject constructor() { + // TODO: b/305234447 update these states from the data layer + val iconViewModel: Flow<IconViewModel> = + flowOf( + IconViewModel( + type = DeviceEntryIconView.IconType.LOCK, + useAodVariant = false, + tint = Color.WHITE, + alpha = 1f, + padding = 48, + ) + ) + val backgroundViewModel: Flow<BackgroundViewModel> = + flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY)) + val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f)) + val isLongPressEnabled: Flow<Boolean> = flowOf(true) + val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = + flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) + + fun onLongPress() { + // TODO() vibrate & perform action based on current lock/unlock state + } + data class BurnInViewModel( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount + ) + + class IconViewModel( + val type: DeviceEntryIconView.IconType, + val useAodVariant: Boolean, + val tint: Int, + val alpha: Float, + val padding: Int, + ) + + class BackgroundViewModel( + val alpha: Float, + val tint: Int, + ) +} diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt index 7a0f4389ed60..c73afe8cf255 100644 --- a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt @@ -14,14 +14,12 @@ * limitations under the License. */ -package android.hardware.biometrics; +package com.android.systemui.log.dagger -/** - * 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 +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for Bluetooth QS tile dialog related logging. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BluetoothTileDialogLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index fd6b3f1e0f45..aecfdaa860f3 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -552,4 +552,12 @@ public class LogModule { public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) { return factory.create("SceneFramework", 50); } + + /** Provides a {@link LogBuffer} for the bluetooth QS tile dialog. */ + @Provides + @SysUISingleton + @BluetoothTileDialogLog + public static LogBuffer provideQBluetoothTileDialogLogBuffer(LogBufferFactory factory) { + return factory.create("BluetoothTileDialogLog", 50); + } } 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 1db31ae4e050..2034d97f211c 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 @@ -47,6 +47,7 @@ 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 dagger.Lazy import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject @@ -62,10 +63,10 @@ constructor( private val context: Context, private val controllerFactory: MediaControllerFactory, private val localMediaManagerFactory: LocalMediaManagerFactory, - private val mr2manager: MediaRouter2Manager, + private val mr2manager: Lazy<MediaRouter2Manager>, private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory, private val configurationController: ConfigurationController, - private val localBluetoothManager: LocalBluetoothManager?, + private val localBluetoothManager: Lazy<LocalBluetoothManager?>, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager, @@ -210,8 +211,8 @@ constructor( fun dump(pw: PrintWriter) { val routingSession = - controller?.let { mr2manager.getRoutingSessionForMediaController(it) } - val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + controller?.let { mr2manager.get().getRoutingSessionForMediaController(it) } + val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) } with(pw) { println(" current device is ${current?.name}") val type = controller?.playbackInfo?.playbackType @@ -355,7 +356,7 @@ constructor( val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice val routingSession = - controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + controller?.let { mr2manager.get().getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device val enabled = device != null && (controller == null || routingSession != null) @@ -380,7 +381,7 @@ constructor( device: MediaDevice?, routingSession: RoutingSessionInfo?, ): String? { - val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) } if (DEBUG) { Log.d( @@ -426,7 +427,9 @@ constructor( return null } + @WorkerThread private fun isLeAudioBroadcastEnabled(): Boolean { + val localBluetoothManager = localBluetoothManager.get() if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager if (profileManager != null) { @@ -446,19 +449,20 @@ constructor( return false } + @WorkerThread private fun getBroadcastingInfo(bluetoothLeBroadcast: LocalBluetoothLeBroadcast) { - var currentBroadcastedApp = bluetoothLeBroadcast.appSourceName + val currentBroadcastedApp = bluetoothLeBroadcast.appSourceName // TODO(b/233698402): Use the package name instead of app label to avoid the // unexpected result. // Check the current media app's name is the same with current broadcast app's name // or not. - var mediaApp = + val mediaApp = MediaDataUtils.getAppLabel( context, localMediaManager.packageName, context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name) ) - var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp) + val isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp) if (isCurrentBroadcastedApp) { broadcastDescription = context.getString(R.string.broadcasting_description_is_broadcasting) diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 38204ab1b6d9..a6c623391bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -64,14 +64,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatterySaverUtils; -import com.android.settingslib.utils.PowerUtil; -import com.android.systemui.res.R; import com.android.systemui.SystemUIApplication; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.BatteryController; @@ -376,14 +375,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { TAG_AUTO_SAVER, SystemMessage.NOTE_AUTO_SAVER_SUGGESTION, n, UserHandle.ALL); } - private String getHybridContentString(String percentage) { - return PowerUtil.getBatteryRemainingStringFormatted( - mContext, - mCurrentBatterySnapshot.getTimeRemainingMillis(), - percentage, - mCurrentBatterySnapshot.isBasedOnUsage()); - } - private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser( mContext, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java index 68bf88b16ae2..3b3844a4be7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java @@ -74,6 +74,7 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(getView()); mQsImpl = mQsImplProvider.get(); + mQsImpl.onCreate(null); mQsImpl.onComponentCreated(qsFragmentComponent, savedInstanceState); } @@ -85,21 +86,13 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS { } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (mQsImpl != null) { - mQsImpl.onCreate(savedInstanceState); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); + public void onDestroyView() { if (mQsImpl != null) { mQsImpl.onDestroy(); + mQsImpl = null; } + super.onDestroyView(); } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 4aad6a069cbb..fab7e952a874 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -170,6 +170,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private CommandQueue mCommandQueue; private View mRootView; + private View mFooterActionsView; @Inject public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -285,6 +286,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS) || !ComposeFacade.INSTANCE.isComposeAvailable()) { Log.d(TAG, "Binding the View implementation of the QS footer actions"); + mFooterActionsView = footerActionsView; mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); return; @@ -294,6 +296,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(), mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); + mFooterActionsView = composeView; // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin // to all views except for qs_footer_actions, so we set it to the Compose view. @@ -472,7 +475,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard); mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); - mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible); + mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (mQsExpanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); @@ -856,7 +859,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl boolean customizing = isCustomizing(); mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); - mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing); + mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. @@ -940,7 +943,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @Override public void dump(PrintWriter pw, String[] args) { IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " "); - indentingPw.println("QSFragment:"); + indentingPw.println("QSImpl:"); indentingPw.increaseIndent(); indentingPw.println("mQsBounds: " + mQsBounds); indentingPw.println("mQsExpanded: " + mQsExpanded); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index fc2f5f9c7226..11db69b69f13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -174,7 +174,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr @Override public void destroy() { - super.destroy(); + // Don't call super as this may be called before the view is dettached and calling super + // will remove the attach listener. We don't need to do that, because once this object is + // detached from the graph, it will be gc. mHost.removeCallback(mQSHostCallback); for (TileRecord record : mRecords) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index a65967a0349b..8f26e694a067 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -31,6 +31,7 @@ import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.pipeline.dagger.QSPipelineModule; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.di.QSTilesModule; import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -60,17 +61,22 @@ import javax.inject.Named; QSFlagsModule.class, QSHostModule.class, QSPipelineModule.class, + QSTilesModule.class, } ) public interface QSModule { - /** A map of internal QS tiles. Ensures that this can be injected even if - * it is empty */ + /** + * A map of internal QS tiles. Ensures that this can be injected even if + * it is empty + */ @Multibinds Map<String, QSTileImpl<?>> tileMap(); - /** A map of internal QS tile ViewModels. Ensures that this can be injected even if - * it is empty */ + /** + * A map of internal QS tile ViewModels. Ensures that this can be injected even if + * it is empty + */ @Multibinds Map<String, QSTileViewModel> tileViewModelMap(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 051eeb0cfaf1..bd13d0686462 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -48,6 +48,7 @@ import android.widget.Switch; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -538,12 +539,17 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, Log.i(TAG, "Launching activity before click"); } else { Log.i(TAG, "The activity is starting"); - ActivityLaunchAnimator.Controller controller = mViewClicked == null - ? null - : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0); - mUiHandler.post(() -> - mActivityStarter.startPendingIntentDismissingKeyguard( - pendingIntent, null, controller) + + ActivityLaunchAnimator.Controller controller = + mViewClicked == null ? null : + ActivityLaunchAnimator.Controller.fromView( + mViewClicked, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ); + mActivityStarter.startPendingIntentMaybeDismissingKeyguard( + pendingIntent, + /* intentSentUiThreadCallback= */ null, + controller ); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index d09b21035cae..0995dd4e592e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -24,13 +24,11 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.dagger.SysUISingleton @@ -40,6 +38,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.res.R import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -98,10 +97,6 @@ class FooterActionsViewBinder @Inject constructor() { var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null var previousUserSwitcher: FooterActionsButtonViewModel? = null - // Set the initial visibility on the View directly so that we don't briefly show it for a - // few frames before [viewModel.isVisible] is collected. - view.isInvisible = !viewModel.isVisible.value - // Listen for ViewModel updates when the View is attached. view.repeatWhenAttached { val attachedScope = this.lifecycleScope @@ -111,12 +106,7 @@ class FooterActionsViewBinder @Inject constructor() { // TODO(b/242040009): Should this move somewhere else? launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) } - // Make sure we set the correct visibility and alpha even when QS are not currently - // shown. - launch { - viewModel.isVisible.collect { isVisible -> view.isInvisible = !isVisible } - } - + // Make sure we set the correct alphas even when QS are not currently shown. launch { viewModel.alpha.collect { view.alpha = it } } launch { viewModel.backgroundAlpha.collect { diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index eff3e76f43be..aff4a6759a47 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -77,14 +77,6 @@ class FooterActionsViewModel( */ val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit, ) { - /** - * Whether the UI rendering this ViewModel should be visible. Note that even when this is false, - * the UI should still participate to the layout it is included in (i.e. in the View world it - * should be INVISIBLE, not GONE). - */ - private val _isVisible = MutableStateFlow(false) - val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() - /** The alpha the UI rendering this ViewModel should have. */ private val _alpha = MutableStateFlow(1f) val alpha: StateFlow<Float> = _alpha.asStateFlow() @@ -93,10 +85,6 @@ class FooterActionsViewModel( private val _backgroundAlpha = MutableStateFlow(1f) val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow() - fun onVisibilityChangeRequested(visible: Boolean) { - _isVisible.value = visible - } - /** Called when the expansion of the Quick Settings changed. */ fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) { if (isInSplitShade) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt index 056f967b09bb..d1f8945cc091 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.base.interactor import android.content.Context +import android.os.UserHandle import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import com.android.settingslib.RestrictedLockUtils @@ -32,7 +33,7 @@ import kotlinx.coroutines.withContext /** * Provides restrictions data for the tiles. This is used in - * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is + * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl] to determine if the tile is * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy]. */ interface DisabledByPolicyInteractor { @@ -41,7 +42,7 @@ interface DisabledByPolicyInteractor { * Checks if the tile is restricted by the policy for a specific user. Pass the result to the * [handlePolicyResult] to let the user know that the tile is disable by the admin. */ - suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult + suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult /** * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this @@ -75,14 +76,14 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) : DisabledByPolicyInteractor { - override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult = + override suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult = withContext(backgroundDispatcher) { val admin: EnforcedAdmin = - restrictedLockProxy.getEnforcedAdmin(userId, userRestriction) + restrictedLockProxy.getEnforcedAdmin(user.identifier, userRestriction) ?: return@withContext PolicyResult.TileEnabled return@withContext if ( - !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction) + !restrictedLockProxy.hasBaseUserRestriction(user.identifier, userRestriction) ) { PolicyResult.TileDisabled(admin) } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt index a3e38500123e..9752fea22f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.base.interactor +import android.os.UserHandle import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -29,13 +30,12 @@ interface QSTileDataInteractor<DATA_TYPE> { /** * Returns a data flow scoped to the user. This means the subscription will live when the tile - * is listened for the [userId]. It's cancelled when the tile is not listened or the user - * changes. + * is listened for the [user]. It's cancelled when the tile is not listened or the user changes. * * You can use [Flow.onStart] on the returned to update the tile with the current state as soon * as possible. */ - fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE> + fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE> /** * Returns tile availability - whether this device currently supports this tile. @@ -43,5 +43,5 @@ interface QSTileDataInteractor<DATA_TYPE> { * You can use [Flow.onStart] on the returned to update the tile with the current state as soon * as possible. */ - fun availability(userId: Int): Flow<Boolean> + fun availability(user: UserHandle): Flow<Boolean> } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt index 102fa3641ff4..77ff6092063f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt @@ -16,11 +16,12 @@ package com.android.systemui.qs.tiles.base.interactor +import android.os.UserHandle import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction /** @see QSTileUserActionInteractor.handleInput */ data class QSTileInput<T>( - val userId: Int, + val user: UserHandle, val action: QSTileUserAction, val data: T, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt new file mode 100644 index 000000000000..936bf9c8f4da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -0,0 +1,125 @@ +/* + * 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.qs.tiles.base.viewmodel + +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.di.QSTileComponent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher + +/** + * Factory to create an appropriate [QSTileViewModelImpl] instance depending on your circumstances. + * + * @see [QSTileViewModelFactory.Component] + * @see [QSTileViewModelFactory.Static] + */ +sealed interface QSTileViewModelFactory<T> { + + /** + * This factory allows you to pass an instance of [QSTileComponent] to a view model effectively + * binding them together. This achieves a DI scope that lives along the instance of + * [QSTileViewModelImpl]. + */ + class Component<T> + @Inject + constructor( + private val disabledByPolicyInteractor: DisabledByPolicyInteractor, + private val userRepository: UserRepository, + private val falsingManager: FalsingManager, + private val qsTileAnalytics: QSTileAnalytics, + private val qsTileLogger: QSTileLogger, + private val systemClock: SystemClock, + @Background private val backgroundDispatcher: CoroutineDispatcher, + ) : QSTileViewModelFactory<T> { + + /** + * Creates [QSTileViewModelImpl] based on the interactors obtained from [component]. + * Reference of that [component] is then stored along the view model. + */ + fun create(component: QSTileComponent<T>): QSTileViewModelImpl<T> = + QSTileViewModelImpl( + component::config, + component::userActionInteractor, + component::dataInteractor, + component::dataToStateMapper, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + systemClock, + backgroundDispatcher, + ) + } + + /** + * This factory passes by necessary implementations to the [QSTileViewModelImpl]. This is a + * default choice for most of the tiles. + */ + class Static<T> + @Inject + constructor( + private val disabledByPolicyInteractor: DisabledByPolicyInteractor, + private val userRepository: UserRepository, + private val falsingManager: FalsingManager, + private val qsTileAnalytics: QSTileAnalytics, + private val qsTileLogger: QSTileLogger, + private val systemClock: SystemClock, + @Background private val backgroundDispatcher: CoroutineDispatcher, + ) : QSTileViewModelFactory<T> { + + /** + * @param config contains all the static information (like TileSpec) about the tile. + * @param userActionInteractor encapsulates user input processing logic. Use it to start + * activities, show dialogs or otherwise update the tile state. + * @param tileDataInteractor provides [DATA_TYPE] and its availability. + * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View + * layer. It's called in [backgroundDispatcher], so it's safe to perform long running + * operations there. + */ + fun create( + config: QSTileConfig, + userActionInteractor: QSTileUserActionInteractor<T>, + tileDataInteractor: QSTileDataInteractor<T>, + mapper: QSTileDataToStateMapper<T>, + ): QSTileViewModelImpl<T> = + QSTileViewModelImpl( + { config }, + { userActionInteractor }, + { tileDataInteractor }, + { mapper }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + systemClock, + backgroundDispatcher, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 14de5eb8be7f..bbb74453abbd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -16,10 +16,7 @@ package com.android.systemui.qs.tiles.base.viewmodel -import androidx.annotation.CallSuper -import androidx.annotation.VisibleForTesting -import com.android.internal.util.Preconditions -import com.android.systemui.dagger.qualifiers.Background +import android.os.UserHandle import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger @@ -30,7 +27,6 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.viewmodel.QSTileConfig -import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction @@ -38,13 +34,11 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.throttle import com.android.systemui.util.time.SystemClock -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -66,19 +60,17 @@ import kotlinx.coroutines.flow.stateIn /** * Provides a hassle-free way to implement new tiles according to current System UI architecture - * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to - * [QSTileLifecycle.ALIVE] state. + * standards. This ViewModel is cheap to instantiate and does nothing until its [state] is listened. * - * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class. + * Don't use this constructor directly. Instead, inject [QSTileViewModelFactory] to create a new + * instance of this class. */ @OptIn(ExperimentalCoroutinesApi::class) -class BaseQSTileViewModel<DATA_TYPE> -@VisibleForTesting -constructor( - override val config: QSTileConfig, - private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, - private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, - private val mapper: QSTileDataToStateMapper<DATA_TYPE>, +class QSTileViewModelImpl<DATA_TYPE>( + val tileConfig: () -> QSTileConfig, + private val userActionInteractor: () -> QSTileUserActionInteractor<DATA_TYPE>, + private val tileDataInteractor: () -> QSTileDataInteractor<DATA_TYPE>, + private val mapper: () -> QSTileDataToStateMapper<DATA_TYPE>, private val disabledByPolicyInteractor: DisabledByPolicyInteractor, userRepository: UserRepository, private val falsingManager: FalsingManager, @@ -86,39 +78,11 @@ constructor( private val qsTileLogger: QSTileLogger, private val systemClock: SystemClock, private val backgroundDispatcher: CoroutineDispatcher, - private val tileScope: CoroutineScope, + private val tileScope: CoroutineScope = CoroutineScope(SupervisorJob()), ) : QSTileViewModel { - @AssistedInject - constructor( - @Assisted config: QSTileConfig, - @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, - @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, - @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>, - disabledByPolicyInteractor: DisabledByPolicyInteractor, - userRepository: UserRepository, - falsingManager: FalsingManager, - qsTileAnalytics: QSTileAnalytics, - qsTileLogger: QSTileLogger, - systemClock: SystemClock, - @Background backgroundDispatcher: CoroutineDispatcher, - ) : this( - config, - userActionInteractor, - tileDataInteractor, - mapper, - disabledByPolicyInteractor, - userRepository, - falsingManager, - qsTileAnalytics, - qsTileLogger, - systemClock, - backgroundDispatcher, - CoroutineScope(SupervisorJob()) - ) - - private val userIds: MutableStateFlow<Int> = - MutableStateFlow(userRepository.getSelectedUserInfo().id) + private val users: MutableStateFlow<UserHandle> = + MutableStateFlow(userRepository.getSelectedUserInfo().userHandle) private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val forceUpdates: MutableSharedFlow<Unit> = @@ -126,12 +90,26 @@ constructor( private val spec get() = config.tileSpec - private lateinit var tileData: SharedFlow<DATA_TYPE> + private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow() - override lateinit var state: SharedFlow<QSTileState> + override val config + get() = tileConfig() + override val state: SharedFlow<QSTileState> = + tileData + .map { data -> + mapper().map(config, data).also { state -> + qsTileLogger.logStateUpdate(spec, state, data) + } + } + .flowOn(backgroundDispatcher) + .shareIn( + tileScope, + SharingStarted.WhileSubscribed(), + replay = 1, + ) override val isAvailable: StateFlow<Boolean> = - userIds - .flatMapLatest { tileDataInteractor.availability(it) } + users + .flatMapLatest { tileDataInteractor().availability(it) } .flowOn(backgroundDispatcher) .stateIn( tileScope, @@ -139,24 +117,15 @@ constructor( true, ) - private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD - - @CallSuper override fun forceUpdate() { - Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE) forceUpdates.tryEmit(Unit) } - @CallSuper - override fun onUserIdChanged(userId: Int) { - Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE) - userIds.tryEmit(userId) + override fun onUserChanged(user: UserHandle) { + users.tryEmit(user) } - @CallSuper override fun onActionPerformed(userAction: QSTileUserAction) { - Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE) - qsTileLogger.logUserAction( userAction, spec, @@ -166,40 +135,16 @@ constructor( userInputs.tryEmit(userAction) } - @CallSuper - override fun onLifecycle(lifecycle: QSTileLifecycle) { - when (lifecycle) { - QSTileLifecycle.ALIVE -> { - Preconditions.checkState(currentLifeState == QSTileLifecycle.DEAD) - tileData = createTileDataFlow() - state = - tileData - .map { data -> - mapper.map(config, data).also { state -> - qsTileLogger.logStateUpdate(spec, state, data) - } - } - .flowOn(backgroundDispatcher) - .shareIn( - tileScope, - SharingStarted.WhileSubscribed(), - replay = 1, - ) - } - QSTileLifecycle.DEAD -> { - Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE) - tileScope.coroutineContext.cancelChildren() - } - } - currentLifeState = lifecycle + override fun destroy() { + tileScope.cancel() } private fun createTileDataFlow(): SharedFlow<DATA_TYPE> = - userIds - .flatMapLatest { userId -> + users + .flatMapLatest { user -> val updateTriggers = merge( - userInputFlow(userId), + userInputFlow(user), forceUpdates .map { DataUpdateTrigger.ForceUpdate } .onEach { qsTileLogger.logForceUpdate(spec) }, @@ -208,8 +153,8 @@ constructor( emit(DataUpdateTrigger.InitialRequest) qsTileLogger.logInitialRequest(spec) } - tileDataInteractor - .tileData(userId, updateTriggers) + tileDataInteractor() + .tileData(user, updateTriggers) .cancellable() .flowOn(backgroundDispatcher) } @@ -228,10 +173,10 @@ constructor( * * Subscribing to the result flow twice will result in doubling all actions, logs and analytics. */ - private fun userInputFlow(userId: Int): Flow<DataUpdateTrigger> { + private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> { return userInputs .filterFalseActions() - .filterByPolicy(userId) + .filterByPolicy(user) .throttle(CLICK_THROTTLE_DURATION, systemClock) // Skip the input until there is some data .mapNotNull { action -> @@ -240,25 +185,27 @@ constructor( qsTileLogger.logUserActionPipeline(spec, action, state, data) qsTileAnalytics.trackUserAction(config, action) - DataUpdateTrigger.UserInput(QSTileInput(userId, action, data)) + DataUpdateTrigger.UserInput(QSTileInput(user, action, data)) } - .onEach { userActionInteractor.handleInput(it.input) } + .onEach { userActionInteractor().handleInput(it.input) } .flowOn(backgroundDispatcher) } - private fun Flow<QSTileUserAction>.filterByPolicy(userId: Int): Flow<QSTileUserAction> = - when (config.policy) { - is QSTilePolicy.NoRestrictions -> this - is QSTilePolicy.Restricted -> - filter { action -> - val result = - disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction) - !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled -> - if (isDisabled) { - qsTileLogger.logUserActionRejectedByPolicy(action, spec) + private fun Flow<QSTileUserAction>.filterByPolicy(user: UserHandle): Flow<QSTileUserAction> = + config.policy.let { policy -> + when (policy) { + is QSTilePolicy.NoRestrictions -> this@filterByPolicy + is QSTilePolicy.Restricted -> + filter { action -> + val result = + disabledByPolicyInteractor.isDisabled(user, policy.userRestriction) + !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled -> + if (isDisabled) { + qsTileLogger.logUserActionRejectedByPolicy(action, spec) + } } } - } + } } private fun Flow<QSTileUserAction>.filterFalseActions(): Flow<QSTileUserAction> = @@ -279,30 +226,4 @@ constructor( private companion object { const val CLICK_THROTTLE_DURATION = 200L } - - /** - * Factory interface for assisted inject. Dagger has bad time supporting generics in assisted - * injection factories now. That's why you need to create an interface implementing this one and - * annotate it with [dagger.assisted.AssistedFactory]. - * - * ex: @AssistedFactory interface FooFactory : BaseQSTileViewModel.Factory<FooData> - */ - interface Factory<T> { - - /** - * @param config contains all the static information (like TileSpec) about the tile. - * @param userActionInteractor encapsulates user input processing logic. Use it to start - * activities, show dialogs or otherwise update the tile state. - * @param tileDataInteractor provides [DATA_TYPE] and its availability. - * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View - * layer. It's called in [backgroundDispatcher], so it's safe to perform long running - * operations there. - */ - fun create( - config: QSTileConfig, - userActionInteractor: QSTileUserActionInteractor<T>, - tileDataInteractor: QSTileDataInteractor<T>, - mapper: QSTileDataToStateMapper<T>, - ): BaseQSTileViewModel<T> - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index d0809c52acd9..0a6becd6e4ca 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter import javax.inject.Inject @@ -38,7 +37,6 @@ constructor( override fun createTile(tileSpec: String): QSTile? = tileMap[tileSpec]?.let { val tile = it.get() - tile.onLifecycle(QSTileLifecycle.ALIVE) adapterFactory.create(tile) } } diff --git a/startop/view_compiler/tinyxml_layout_parser.cc b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 1b3a81f17976..94b39b6db9d2 100644 --- a/startop/view_compiler/tinyxml_layout_parser.cc +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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. @@ -13,22 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "tinyxml_layout_parser.h" -#include "layout_validation.h" +package com.android.systemui.qs.tiles.di -namespace startop { +import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent +import dagger.Module -bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) { - LayoutValidationVisitor validator; - TinyXmlVisitorAdapter adapter{&validator}; - xml.Accept(&adapter); - - if (message != nullptr) { - *message = validator.message(); - } - - return validator.can_compile(); -} - -} // namespace startop +/** Module listing subcomponents */ +@Module( + subcomponents = + [ + CustomTileComponent::class, + ] +) +interface QSTilesModule diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt index 8957fc3efb9c..9c63a30dfc1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.dialog.bluetooth +import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.STATE_OFF import android.bluetooth.BluetoothAdapter.STATE_ON import com.android.settingslib.bluetooth.BluetoothCallback @@ -37,6 +38,7 @@ internal class BluetoothStateInteractor @Inject constructor( private val localBluetoothManager: LocalBluetoothManager?, + private val logger: BluetoothTileDialogLogger, @Application private val coroutineScope: CoroutineScope, ) { @@ -47,6 +49,10 @@ constructor( override fun onBluetoothStateChanged(bluetoothState: Int) { if (bluetoothState == STATE_ON || bluetoothState == STATE_OFF) { super.onBluetoothStateChanged(bluetoothState) + logger.logBluetoothState( + BluetoothStateStage.BLUETOOTH_STATE_CHANGE_RECEIVED, + BluetoothAdapter.nameForState(bluetoothState) + ) trySendWithFailureLogging( bluetoothState == STATE_ON, TAG, @@ -70,6 +76,10 @@ constructor( if (isBluetoothEnabled != value) { localBluetoothManager?.bluetoothAdapter?.apply { if (value) enable() else disable() + logger.logBluetoothState( + BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, + value.toString() + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 80af76de23df..409622615b6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -34,6 +34,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.time.SystemClock import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -46,7 +47,9 @@ constructor( private val bluetoothToggleInitialValue: Boolean, private val subtitleResIdInitialValue: Int, private val bluetoothTileDialogCallback: BluetoothTileDialogCallback, + private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, + private val logger: BluetoothTileDialogLogger, context: Context, ) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) { @@ -102,9 +105,11 @@ constructor( showSeeAll: Boolean, showPairNewDevice: Boolean ) { + val start = systemClock.elapsedRealtime() deviceItemAdapter.refreshDeviceItemList(deviceItem) { seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start) } } @@ -117,6 +122,7 @@ constructor( toggleView.isChecked = bluetoothToggleInitialValue toggleView.setOnCheckedChangeListener { _, isChecked -> mutableBluetoothStateToggle.value = isChecked + logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString()) uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt new file mode 100644 index 000000000000..5d18dc1d453a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt @@ -0,0 +1,106 @@ +/* + * 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.qs.tiles.dialog.bluetooth + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.dagger.BluetoothTileDialogLog +import javax.inject.Inject + +private const val TAG = "BluetoothTileDialogLog" + +enum class BluetoothStateStage { + USER_TOGGLED, + BLUETOOTH_STATE_VALUE_SET, + BLUETOOTH_STATE_CHANGE_RECEIVED +} + +enum class DeviceFetchTrigger { + FIRST_LOAD, + BLUETOOTH_STATE_CHANGE_RECEIVED, + BLUETOOTH_CALLBACK_RECEIVED +} + +enum class JobStatus { + FINISHED, + CANCELLED +} + +class BluetoothTileDialogLogger +@Inject +constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) { + + fun logBluetoothState(stage: BluetoothStateStage, state: String) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = stage.toString() + str2 = state + }, + { "BluetoothState. stage=$str1 state=$str2" } + ) + + fun logDeviceClick(address: String, type: DeviceItemType) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = address + str2 = type.toString() + }, + { "DeviceClick. address=$str1 type=$str2" } + ) + + fun logActiveDeviceChanged(address: String?, profileId: Int) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = address + int1 = profileId + }, + { "ActiveDeviceChanged. address=$str1 profileId=$int1" } + ) + + fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = address + str2 = state + int1 = profileId + }, + { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" } + ) + + fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = status.toString() + str2 = trigger.toString() + long1 = duration + }, + { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" } + ) + + fun logDeviceUiUpdate(duration: Long) = + logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" }) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt index 2865ad7a3e83..86e5ddef87e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt @@ -28,7 +28,10 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent @UiEvent(doc = "Gear icon clicked") DEVICE_GEAR_CLICKED(1497), @UiEvent(doc = "Device clicked") DEVICE_CLICKED(1498), @UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499), - @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500); + @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500), + @UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507), + @UiEvent(doc = "Connected other device clicked to disconnect") + CONNECTED_OTHER_DEVICE_DISCONNECT(1508); override fun getId() = metricId } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 8e274932d4d2..f7e0de3ed883 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -35,10 +35,12 @@ import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Compan import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -53,7 +55,9 @@ constructor( private val bluetoothStateInteractor: BluetoothStateInteractor, private val dialogLaunchAnimator: DialogLaunchAnimator, private val activityStarter: ActivityStarter, + private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, + private val logger: BluetoothTileDialogLogger, @Application private val coroutineScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, ) : BluetoothTileDialogCallback { @@ -90,7 +94,11 @@ constructor( } ?: dialog!!.show() updateDeviceItemJob?.cancel() - updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context) } + updateDeviceItemJob = launch { + // Add a slight delay for smoother dialog bounds change + delay(FIRST_LOAD_DELAY_MS) + deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) + } bluetoothStateInteractor.bluetoothStateUpdate .filterNotNull() @@ -98,7 +106,10 @@ constructor( dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it)) updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { - deviceItemInteractor.updateDeviceItems(context) + deviceItemInteractor.updateDeviceItems( + context, + DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED + ) } } .launchIn(this) @@ -107,7 +118,10 @@ constructor( .onEach { updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { - deviceItemInteractor.updateDeviceItems(context) + deviceItemInteractor.updateDeviceItems( + context, + DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED + ) } } .launchIn(this) @@ -139,7 +153,9 @@ constructor( bluetoothStateInteractor.isBluetoothEnabled, getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled), this@BluetoothTileDialogViewModel, + systemClock, uiEventLogger, + logger, context ) .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } } @@ -189,6 +205,7 @@ constructor( companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" + private const val FIRST_LOAD_DELAY_MS = 500L private fun getSubtitleResId(isBluetoothEnabled: Boolean) = if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle else R.string.bt_is_off diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt index 50eaf38f20d6..2c8d2a0806d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt @@ -36,6 +36,7 @@ import android.graphics.drawable.Drawable import com.android.settingslib.bluetooth.CachedBluetoothDevice enum class DeviceItemType { + ACTIVE_MEDIA_BLUETOOTH_DEVICE, AVAILABLE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE, SAVED_BLUETOOTH_DEVICE, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt index 8c22614f9c31..7bb1619c5001 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt @@ -39,12 +39,38 @@ internal abstract class DeviceItemFactory { abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem } +internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() { + override fun isFilterMatched( + cachedDevice: CachedBluetoothDevice, + audioManager: AudioManager? + ): Boolean { + return BluetoothUtils.isActiveMediaDevice(cachedDevice) && + BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager) + } + + override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem { + return DeviceItem( + type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE, + cachedBluetoothDevice = cachedDevice, + deviceName = cachedDevice.name, + connectionSummary = cachedDevice.connectionSummary ?: "", + iconWithDescription = + BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> + Pair(p.first, p.second) + }, + background = backgroundOn, + isEnabled = !cachedDevice.isBusy, + ) + } +} + internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { override fun isFilterMatched( cachedDevice: CachedBluetoothDevice, audioManager: AudioManager? ): Boolean { - return BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager) + return !BluetoothUtils.isActiveMediaDevice(cachedDevice) && + BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager) } // TODO(b/298124674): move create() to the abstract class to reduce duplicate code @@ -59,7 +85,7 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> Pair(p.first, p.second) }, - background = backgroundOn, + background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, ) } @@ -84,7 +110,7 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> Pair(p.first, p.second) }, - background = backgroundOn, + background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt index e196c6c27c8b..76fbf8e427e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt @@ -22,7 +22,6 @@ import android.content.Context import android.media.AudioManager import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.BluetoothCallback -import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -30,6 +29,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext /** Holds business logic for the Bluetooth Dialog after clicking on the Bluetooth QS tile. */ @@ -50,7 +51,9 @@ constructor( private val audioManager: AudioManager, private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(), private val localBluetoothManager: LocalBluetoothManager?, + private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, + private val logger: BluetoothTileDialogLogger, @Application private val coroutineScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { @@ -69,22 +72,10 @@ constructor( bluetoothProfile: Int ) { super.onActiveDeviceChanged(activeDevice, bluetoothProfile) + logger.logActiveDeviceChanged(activeDevice?.address, bluetoothProfile) trySendWithFailureLogging(Unit, TAG, "onActiveDeviceChanged") } - override fun onConnectionStateChanged( - cachedDevice: CachedBluetoothDevice?, - state: Int - ) { - super.onConnectionStateChanged(cachedDevice, state) - trySendWithFailureLogging(Unit, TAG, "onConnectionStateChanged") - } - - override fun onDeviceAdded(cachedDevice: CachedBluetoothDevice) { - super.onDeviceAdded(cachedDevice) - trySendWithFailureLogging(Unit, TAG, "onDeviceAdded") - } - override fun onProfileConnectionStateChanged( cachedDevice: CachedBluetoothDevice, state: Int, @@ -95,8 +86,24 @@ constructor( state, bluetoothProfile ) + logger.logProfileConnectionStateChanged( + cachedDevice.address, + state.toString(), + bluetoothProfile + ) trySendWithFailureLogging(Unit, TAG, "onProfileConnectionStateChanged") } + + override fun onAclConnectionStateChanged( + cachedDevice: CachedBluetoothDevice, + state: Int + ) { + super.onAclConnectionStateChanged(cachedDevice, state) + // Listen only when a device is disconnecting + if (state == 0) { + trySendWithFailureLogging(Unit, TAG, "onAclConnectionStateChanged") + } + } } localBluetoothManager?.eventManager?.registerCallback(listener) awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) } @@ -105,6 +112,7 @@ constructor( private var deviceItemFactoryList: List<DeviceItemFactory> = listOf( + ActiveMediaDeviceItemFactory(), AvailableMediaDeviceItemFactory(), ConnectedDeviceItemFactory(), SavedDeviceItemFactory() @@ -112,14 +120,16 @@ constructor( private var displayPriority: List<DeviceItemType> = listOf( + DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE, DeviceItemType.SAVED_BLUETOOTH_DEVICE, ) - internal suspend fun updateDeviceItems(context: Context) { + internal suspend fun updateDeviceItems(context: Context, trigger: DeviceFetchTrigger) { withContext(backgroundDispatcher) { - mutableDeviceItemUpdate.tryEmit( + val start = systemClock.elapsedRealtime() + val deviceItems = bluetoothTileDialogRepository.cachedDevices .mapNotNull { cachedDevice -> deviceItemFactoryList @@ -127,7 +137,22 @@ constructor( ?.create(context, cachedDevice) } .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices) - ) + + // Only emit when the job is not cancelled + if (isActive) { + mutableDeviceItemUpdate.tryEmit(deviceItems) + logger.logDeviceFetch( + JobStatus.FINISHED, + trigger, + systemClock.elapsedRealtime() - start + ) + } else { + logger.logDeviceFetch( + JobStatus.CANCELLED, + trigger, + systemClock.elapsedRealtime() - start + ) + } } } @@ -144,17 +169,26 @@ constructor( } internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) { - when (deviceItem.type) { - DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { - if (!BluetoothUtils.isActiveMediaDevice(deviceItem.cachedBluetoothDevice)) { - deviceItem.cachedBluetoothDevice.setActive() + logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type) + + deviceItem.cachedBluetoothDevice.apply { + when (deviceItem.type) { + DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT) + } + DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { + setActive() uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) } - } - DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {} - DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { - deviceItem.cachedBluetoothDevice.connect() - uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) + DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT) + } + DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { + connect() + uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt new file mode 100644 index 000000000000..bb5a229a0696 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt @@ -0,0 +1,36 @@ +/* + * 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.qs.tiles.impl.custom + +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent + +data class CustomTileData( + val user: UserHandle, + val componentName: ComponentName, + val tile: Tile, + val callingAppUid: Int, + val isActive: Boolean, + val hasPendingBind: Boolean, + val shouldShowChevron: Boolean, + val defaultTileLabel: CharSequence?, + val defaultTileIcon: Icon?, + val component: CustomTileBoundComponent, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt new file mode 100644 index 000000000000..761274e96e43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt @@ -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 com.android.systemui.qs.tiles.impl.custom + +import android.os.UserHandle +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@QSTileScope +class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<CustomTileData> { + TODO("Not yet implemented") + } + + override fun availability(user: UserHandle): Flow<Boolean> { + TODO("Not yet implemented") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt new file mode 100644 index 000000000000..f7bec024b7bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.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.qs.tiles.impl.custom + +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import javax.inject.Inject + +@QSTileScope +class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> { + + override fun map(config: QSTileConfig, data: CustomTileData): QSTileState { + TODO("Not yet implemented") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt new file mode 100644 index 000000000000..6c1c1a34abc0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.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.qs.tiles.impl.custom + +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import javax.inject.Inject + +@QSTileScope +class CustomTileUserActionInteractor @Inject constructor() : + QSTileUserActionInteractor<CustomTileData> { + + override suspend fun handleInput(input: QSTileInput<CustomTileData>) { + TODO("Not yet implemented") + } +} diff --git a/services/core/java/com/android/server/BrickReceiver.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt index cff3805b68d2..01df90662579 100644 --- a/services/core/java/com/android/server/BrickReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 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. @@ -14,18 +14,21 @@ * limitations under the License. */ -package com.android.server; +package com.android.systemui.qs.tiles.impl.custom.di -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.os.SystemService; -import android.util.Slog; +import com.android.systemui.qs.tiles.impl.di.QSTileComponent +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import dagger.Subcomponent -public class BrickReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!"); - SystemService.start("brick"); +@QSTileScope +@Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class]) +interface CustomTileComponent : QSTileComponent<Any> { + + @Subcomponent.Builder + interface Builder { + + fun qsTileConfigModule(module: QSTileConfigModule): Builder + + fun build(): CustomTileComponent } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt new file mode 100644 index 000000000000..ccff8afe7628 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.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.systemui.qs.tiles.impl.custom.di + +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.custom.CustomTileData +import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor +import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper +import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent +import dagger.Binds +import dagger.Module + +/** Provides bindings for QSTile interfaces */ +@Module(subcomponents = [CustomTileBoundComponent::class]) +interface CustomTileModule { + + @Binds + fun bindDataInteractor( + dataInteractor: CustomTileInteractor + ): QSTileDataInteractor<CustomTileData> + + @Binds + fun bindUserActionInteractor( + userActionInteractor: CustomTileUserActionInteractor + ): QSTileUserActionInteractor<CustomTileData> + + @Binds + fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData> +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt new file mode 100644 index 000000000000..558fb64e3ef8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/QSTileConfigModule.kt @@ -0,0 +1,43 @@ +/* + * 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.qs.tiles.impl.custom.di + +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import dagger.Module +import dagger.Provides + +/** + * Provides [QSTileConfig] and [TileSpec]. To be used along in a QS tile scoped component + * implementing [com.android.systemui.qs.tiles.impl.di.QSTileComponent]. In that case it makes it + * possible to inject config and tile spec associated with the current tile + */ +@Module +class QSTileConfigModule(private val config: QSTileConfig) { + + @Provides fun provideConfig(): QSTileConfig = config + + @Provides fun provideTileSpec(): TileSpec = config.tileSpec + + @Provides + fun provideCustomTileSpec(): TileSpec.CustomTileSpec = + config.tileSpec as TileSpec.CustomTileSpec + + @Provides + fun providePlatformTileSpec(): TileSpec.PlatformTileSpec = + config.tileSpec as TileSpec.PlatformTileSpec +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt new file mode 100644 index 000000000000..e33b3e917629 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt @@ -0,0 +1,36 @@ +/* + * 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.qs.tiles.impl.custom.di.bound + +import android.os.UserHandle +import dagger.BindsInstance +import dagger.Subcomponent +import kotlinx.coroutines.CoroutineScope + +/** @see CustomTileBoundScope */ +@CustomTileBoundScope +@Subcomponent +interface CustomTileBoundComponent { + + @Subcomponent.Builder + interface Builder { + @BindsInstance fun user(@CustomTileUser user: UserHandle): Builder + @BindsInstance fun coroutineScope(@CustomTileBoundScope scope: CoroutineScope): Builder + + fun build(): CustomTileBoundComponent + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt new file mode 100644 index 000000000000..4a4ba2bf7ef9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt @@ -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 com.android.systemui.qs.tiles.impl.custom.di.bound + +import javax.inject.Scope + +/** + * Scope annotation for bound custom tile scope. This scope lives when a particular + * [com.android.systemui.qs.external.CustomTile] is listening and bound to the + * [android.service.quicksettings.TileService]. + */ +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Scope +annotation class CustomTileBoundScope diff --git a/startop/view_compiler/util.h b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt index 0176175920c1..efc743127418 100644 --- a/startop/view_compiler/util.h +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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. @@ -13,17 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef VIEW_COMPILER_UTIL_H_ -#define VIEW_COMPILER_UTIL_H_ -#include <string> +package com.android.systemui.qs.tiles.impl.custom.di.bound -namespace startop { -namespace util { +import javax.inject.Qualifier -std::string FindLayoutNameFromFilename(const std::string& filename); - -} // namespace util -} // namespace startop - -#endif // VIEW_COMPILER_UTIL_H_ +/** User associated with current custom tile binding. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class CustomTileUser diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt new file mode 100644 index 000000000000..6f351cdb9b33 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt @@ -0,0 +1,38 @@ +/* + * 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.qs.tiles.impl.di + +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig + +/** + * Base QS tile component. It should be used with [QSTileScope] to create a custom tile scoped + * component. Pass this component to + * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory.Component]. + */ +interface QSTileComponent<T> { + + fun dataInteractor(): QSTileDataInteractor<T> + + fun userActionInteractor(): QSTileUserActionInteractor<T> + + fun config(): QSTileConfig + + fun dataToStateMapper(): QSTileDataToStateMapper<T> +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.kt new file mode 100644 index 000000000000..a412de364c19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileScope.kt @@ -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 com.android.systemui.qs.tiles.impl.di + +import javax.inject.Scope + +/** + * Scope annotation for QS tiles. This scope is created for each tile and is disposed when the tile + * is no longer needed (ex. it's removed from QS). So, it lives along the instance of + * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl]. This doesn't align with tile + * visibility. For example, the tile scope survives shade open/close. + */ +@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class QSTileScope diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt index e5cb7ea3e098..580c42122a85 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.viewmodel +import android.os.UserHandle import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow @@ -29,38 +30,32 @@ import kotlinx.coroutines.flow.StateFlow */ interface QSTileViewModel { - /** - * State of the tile to be shown by the view. It's guaranteed that it's only accessed between - * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD]. - */ + /** State of the tile to be shown by the view. */ val state: SharedFlow<QSTileState> val config: QSTileConfig - /** - * Specifies whether this device currently supports this tile. This might be called outside of - * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds (for example in Edit Mode). - */ + /** Specifies whether this device currently supports this tile. */ val isAvailable: StateFlow<Boolean> /** - * Handles ViewModel lifecycle. Implementations should be inactive outside of - * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds. - */ - fun onLifecycle(lifecycle: QSTileLifecycle) - - /** * Notifies about the user change. Implementations should avoid using 3rd party userId sources * and use this value instead. This is to maintain consistent and concurrency-free behaviour * across different parts of QS. */ - fun onUserIdChanged(userId: Int) + fun onUserChanged(user: UserHandle) /** Triggers the emission of the new [QSTileState] in a [state]. */ fun forceUpdate() /** Notifies underlying logic about user input. */ fun onActionPerformed(userAction: QSTileUserAction) + + /** + * Frees the resources held by this view model. Call it when you no longer need the instance, + * because there is no guarantee it will work as expected beyond this point. + */ + fun destroy() } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 33f55ab53233..28536f5b19a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.Context +import android.os.UserHandle import android.util.Log import android.view.View import androidx.annotation.GuardedBy @@ -134,7 +135,7 @@ constructor( qsTileViewModel.currentState?.supportedActions?.contains(action) == true override fun userSwitch(currentUser: Int) { - qsTileViewModel.onUserIdChanged(currentUser) + qsTileViewModel.onUserChanged(UserHandle.of(currentUser)) } @Deprecated( @@ -180,7 +181,7 @@ constructor( override fun destroy() { stateJob?.cancel() availabilityJob?.cancel() - qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD) + qsTileViewModel.destroy() } override fun getState(): QSTile.State? = diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index f0650d34fc9d..9ba02b1aa9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -17,6 +17,8 @@ package com.android.systemui.scene.shared.flag import androidx.annotation.VisibleForTesting +import com.android.systemui.FeatureFlags +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic @@ -47,15 +49,15 @@ interface SceneContainerFlags { class SceneContainerFlagsImpl @AssistedInject constructor( - private val featureFlags: FeatureFlagsClassic, + private val featureFlagsClassic: FeatureFlagsClassic, + featureFlags: FeatureFlags, @Assisted private val isComposeAvailable: Boolean, ) : SceneContainerFlags { companion object { @VisibleForTesting - val flags: List<Flag<Boolean>> = + val classicFlagTokens: List<Flag<Boolean>> = listOf( - Flags.SCENE_CONTAINER, Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, Flags.MIGRATE_LOCK_ICON, Flags.MIGRATE_NSSL, @@ -67,7 +69,13 @@ constructor( /** The list of requirements, all must be met for the feature to be enabled. */ private val requirements = - flags.map { FlagMustBeEnabled(it) } + + listOf( + AconfigFlagMustBeEnabled( + flagName = AConfigFlags.FLAG_SCENE_CONTAINER, + flagValue = featureFlags.sceneContainer(), + ), + ) + + classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } + listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled()) override fun isEnabled(): Boolean { @@ -115,14 +123,25 @@ constructor( override fun isMet(): Boolean { return when (flag) { - is ResourceBooleanFlag -> featureFlags.isEnabled(flag) - is ReleasedFlag -> featureFlags.isEnabled(flag) - is UnreleasedFlag -> featureFlags.isEnabled(flag) + is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag) + is ReleasedFlag -> featureFlagsClassic.isEnabled(flag) + is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag) else -> error("Unsupported flag type ${flag.javaClass}") } } } + private inner class AconfigFlagMustBeEnabled( + flagName: String, + private val flagValue: Boolean, + ) : Requirement { + override val name: String = "Aconfig flag $flagName must be enabled" + + override fun isMet(): Boolean { + return flagValue + } + } + @AssistedFactory interface Factory { fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 3d3447b921fc..a2627eddf05d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -65,7 +65,7 @@ import com.android.systemui.statusbar.NotificationInsetsController; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -182,7 +182,7 @@ public class NotificationShadeWindowViewController implements Dumpable { PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, CommunalViewModel communalViewModel, CommunalRepository communalRepository, - NotificationExpansionRepository notificationExpansionRepository, + NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor, FeatureFlagsClassic featureFlagsClassic, SystemClock clock, BouncerMessageInteractor bouncerMessageInteractor, @@ -239,7 +239,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mLockscreenToDreamingTransition); collectFlow( mView, - notificationExpansionRepository.isExpandAnimationRunning(), + notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), this::setExpandAnimationRunning); mClock = clock; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 2f1b589899a0..dd24ca78005f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1457,7 +1457,7 @@ public class KeyguardIndicationController { } private boolean canUnlockWithFingerprint() { - return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + return mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible( getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 756151bd57e0..96279e2d2e44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -21,7 +21,7 @@ import android.view.ViewGroup import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.LaunchAnimator -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.policy.HeadsUpManager @@ -33,7 +33,7 @@ private const val TAG = "NotificationLaunchAnimatorController" /** A provider of [NotificationLaunchAnimatorController]. */ class NotificationLaunchAnimatorControllerProvider( - private val notificationExpansionRepository: NotificationExpansionRepository, + private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, private val notificationListContainer: NotificationListContainer, private val headsUpManager: HeadsUpManager, private val jankMonitor: InteractionJankMonitor @@ -44,7 +44,7 @@ class NotificationLaunchAnimatorControllerProvider( onFinishAnimationCallback: Runnable? = null ): NotificationLaunchAnimatorController { return NotificationLaunchAnimatorController( - notificationExpansionRepository, + notificationLaunchAnimationInteractor, notificationListContainer, headsUpManager, notification, @@ -60,7 +60,7 @@ class NotificationLaunchAnimatorControllerProvider( * notification expanding into an opening window. */ class NotificationLaunchAnimatorController( - private val notificationExpansionRepository: NotificationExpansionRepository, + private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, private val notificationListContainer: NotificationListContainer, private val headsUpManager: HeadsUpManager, private val notification: ExpandableNotificationRow, @@ -143,7 +143,7 @@ class NotificationLaunchAnimatorController( if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)") } - notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate) + notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate) notificationEntry.isExpandAnimationRunning = willAnimate if (!willAnimate) { @@ -180,7 +180,7 @@ class NotificationLaunchAnimatorController( // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started // here? - notificationExpansionRepository.setIsExpandAnimationRunning(false) + notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false) notificationEntry.isExpandAnimationRunning = false removeHun(animate = true) onFinishAnimationCallback?.run() @@ -200,7 +200,7 @@ class NotificationLaunchAnimatorController( jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) notification.isExpandAnimationRunning = false - notificationExpansionRepository.setIsExpandAnimationRunning(false) + notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false) notificationEntry.isExpandAnimationRunning = false notificationListContainer.setExpandingNotification(null) applyParams(null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 8561869af352..fa366c65b4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -54,7 +54,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule; -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; import com.android.systemui.statusbar.notification.icon.ConversationIconManager; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.init.NotificationsController; @@ -204,12 +204,12 @@ public interface NotificationsModule { @Provides @SysUISingleton static NotificationLaunchAnimatorControllerProvider provideNotifLaunchAnimControllerProvider( - NotificationExpansionRepository notificationExpansionRepository, + NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor, NotificationListContainer notificationListContainer, HeadsUpManager headsUpManager, InteractionJankMonitor jankMonitor) { return new NotificationLaunchAnimatorControllerProvider( - notificationExpansionRepository, + notificationLaunchAnimationInteractor, notificationListContainer, headsUpManager, jankMonitor); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt new file mode 100644 index 000000000000..9b562991f9a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt @@ -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 com.android.systemui.statusbar.notification.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** A repository tracking the status of notification launch animations. */ +@SysUISingleton +class NotificationLaunchAnimationRepository @Inject constructor() { + val isLaunchAnimationRunning = MutableStateFlow(false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt index 6f0a97adb311..22ce4f11b661 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt @@ -14,22 +14,20 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.data.repository +package com.android.systemui.statusbar.notification.domain.interactor import android.util.Log import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow - -private const val TAG = "NotificationExpansionRepository" +import kotlinx.coroutines.flow.StateFlow /** A repository tracking the status of notification expansion animations. */ @SysUISingleton -class NotificationExpansionRepository @Inject constructor() { - private val _isExpandAnimationRunning = MutableStateFlow(false) +class NotificationLaunchAnimationInteractor +@Inject +constructor(private val repository: NotificationLaunchAnimationRepository) { /** * Emits true if an animation that expands a notification object into an opening window is @@ -37,13 +35,18 @@ class NotificationExpansionRepository @Inject constructor() { * * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController]. */ - val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow() + val isLaunchAnimationRunning: StateFlow<Boolean> + get() = repository.isLaunchAnimationRunning - /** Sets whether the notification expansion animation is currently running. */ - fun setIsExpandAnimationRunning(running: Boolean) { + /** Sets whether the notification expansion launch animation is currently running. */ + fun setIsLaunchAnimationRunning(running: Boolean) { if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { - Log.d(TAG, "setIsExpandAnimationRunning(running=$running)") + Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)") } - _isExpandAnimationRunning.value = running + repository.isLaunchAnimationRunning.value = running + } + + companion object { + private const val TAG = "NotificationLaunchAnimationInteractor" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 4a823a40a272..2fffd379e6f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -239,11 +239,11 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun logNoPulsingNotificationHidden(entry: NotificationEntry) { + fun logNoPulsingNotificationHiddenOverride(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey }, { - "No pulsing: notification hidden on lock screen: $str1" + "No pulsing: notification hidden on lock screen by override: $str1" }) } @@ -290,11 +290,11 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun keyguardHideNotification(entry: NotificationEntry) { + fun logNoAlertingNotificationHidden(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey }, { - "Keyguard Hide Notification: $str1" + "No alerting: notification hidden on lock screen: $str1" }) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 3819843aa7b2..75b35f1a9302 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -505,7 +505,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter if (entry.getRanking().getLockscreenVisibilityOverride() == Notification.VISIBILITY_PRIVATE) { - if (log) mLogger.logNoPulsingNotificationHidden(entry); + if (log) mLogger.logNoPulsingNotificationHiddenOverride(entry); return false; } @@ -536,7 +536,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { - if (log) mLogger.keyguardHideNotification(entry); + if (log) mLogger.logNoAlertingNotificationHidden(entry); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 07d3a1cf24c0..2d125462b16e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -30,7 +30,6 @@ import android.view.View import android.view.WindowManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter import com.android.systemui.animation.DelegateLaunchAnimatorController @@ -43,6 +42,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter.OnDismissAction +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController @@ -134,6 +134,19 @@ constructor( ) } + override fun startPendingIntentMaybeDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable?, + animationController: ActivityLaunchAnimator.Controller? + ) { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + intentSentUiThreadCallback = intentSentUiThreadCallback, + animationController = animationController, + showOverLockscreen = true, + ) + } + /** * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate * this. @@ -454,7 +467,7 @@ constructor( !willLaunchResolverActivity && shouldAnimateLaunch(isActivityIntent = true) val animController = - wrapAnimationController( + wrapAnimationControllerForShadeOrStatusBar( animationController = animationController, dismissShade = dismissShade, isLaunchForActivity = true, @@ -547,12 +560,18 @@ constructor( ) } - /** Starts a pending intent after dismissing keyguard. */ + /** + * Starts a pending intent after dismissing keyguard. + * + * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in + * the main thread). + */ fun startPendingIntentDismissingKeyguard( intent: PendingIntent, intentSentUiThreadCallback: Runnable? = null, associatedView: View? = null, animationController: ActivityLaunchAnimator.Controller? = null, + showOverLockscreen: Boolean = false, ) { val animationController = if (associatedView is ExpandableNotificationRow) { @@ -566,79 +585,103 @@ constructor( lockScreenUserManager.currentUserId, )) + val actuallyShowOverLockscreen = + showOverLockscreen && + intent.isActivity && + activityIntentHelper.wouldPendingShowOverLockscreen( + intent, + lockScreenUserManager.currentUserId + ) + val animate = !willLaunchResolverActivity && animationController != null && - shouldAnimateLaunch(intent.isActivity) + shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen) + + // We wrap animationCallback with a StatusBarLaunchAnimatorController so + // that the shade is collapsed after the animation (or when it is cancelled, + // aborted, etc). + val statusBarController = + wrapAnimationControllerForShadeOrStatusBar( + animationController = animationController, + dismissShade = true, + isLaunchForActivity = intent.isActivity, + ) + val controller = + if (actuallyShowOverLockscreen) { + wrapAnimationControllerForLockscreen(statusBarController) + } else { + statusBarController + } // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we // run the animation on the keyguard). The animation will take care of (instantly) // collapsing the shade and hiding the keyguard once it is done. val collapse = !animate - executeRunnableDismissingKeyguard( - runnable = { - try { - // We wrap animationCallback with a StatusBarLaunchAnimatorController so - // that the shade is collapsed after the animation (or when it is cancelled, - // aborted, etc). - val controller: ActivityLaunchAnimator.Controller? = - wrapAnimationController( - animationController = animationController, - dismissShade = true, - isLaunchForActivity = intent.isActivity, - ) - activityLaunchAnimator.startPendingIntentWithAnimation( - controller, - animate, - intent.creatorPackage, - object : PendingIntentStarter { - override fun startPendingIntent( - animationAdapter: RemoteAnimationAdapter? - ): Int { - val options = - ActivityOptions( - CentralSurfaces.getActivityOptions( - displayId, - animationAdapter - ) + val runnable = Runnable { + try { + activityLaunchAnimator.startPendingIntentWithAnimation( + controller, + animate, + intent.creatorPackage, + actuallyShowOverLockscreen, + object : PendingIntentStarter { + override fun startPendingIntent( + animationAdapter: RemoteAnimationAdapter? + ): Int { + val options = + ActivityOptions( + CentralSurfaces.getActivityOptions( + displayId, + animationAdapter ) - // TODO b/221255671: restrict this to only be set for - // notifications - options.isEligibleForLegacyPermissionPrompt = true - options.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED ) - return intent.sendAndReturnResult( - null, - 0, - null, - null, - null, - null, - options.toBundle() - ) - } - }, - ) - } catch (e: PendingIntent.CanceledException) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending intent failed: $e") - if (!collapse) { - // executeRunnableDismissingKeyguard did not collapse for us already. - shadeControllerLazy.get().collapseOnMainThread() - } - // TODO: Dismiss Keyguard. - } - if (intent.isActivity) { - assistManagerLazy.get().hideAssist() + // TODO b/221255671: restrict this to only be set for + // notifications + options.isEligibleForLegacyPermissionPrompt = true + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + return intent.sendAndReturnResult( + null, + 0, + null, + null, + null, + null, + options.toBundle() + ) + } + }, + ) + } catch (e: PendingIntent.CanceledException) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: $e") + if (!collapse) { + // executeRunnableDismissingKeyguard did not collapse for us already. + shadeControllerLazy.get().collapseOnMainThread() } - intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) } - }, - afterKeyguardGone = willLaunchResolverActivity, - dismissShade = collapse, - willAnimateOnKeyguard = animate, - ) + // TODO: Dismiss Keyguard. + } + if (intent.isActivity) { + assistManagerLazy.get().hideAssist() + } + intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) } + } + + if (!actuallyShowOverLockscreen) { + postOnUiThread(delay = 0) { + executeRunnableDismissingKeyguard( + runnable = runnable, + afterKeyguardGone = willLaunchResolverActivity, + dismissShade = collapse, + willAnimateOnKeyguard = animate, + ) + } + } else { + postOnUiThread(delay = 0, runnable) + } } /** Starts an Activity. */ @@ -678,71 +721,12 @@ constructor( // Wrap the animation controller to dismiss the shade and set // mIsLaunchingActivityOverLockscreen during the animation. val delegate = - wrapAnimationController( + wrapAnimationControllerForShadeOrStatusBar( animationController = animationController, dismissShade = dismissShade, isLaunchForActivity = true, ) - delegate?.let { - controller = - object : DelegateLaunchAnimatorController(delegate) { - override fun onIntentStarted(willAnimate: Boolean) { - delegate?.onIntentStarted(willAnimate) - if (willAnimate) { - centralSurfaces?.setIsLaunchingActivityOverLockscreen(true) - } - } - - override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { - super.onLaunchAnimationStart(isExpandingFullyAbove) - - // Double check that the keyguard is still showing and not going - // away, but if so set the keyguard occluded. Typically, WM will let - // KeyguardViewMediator know directly, but we're overriding that to - // play the custom launch animation, so we need to take care of that - // here. The unocclude animation is not overridden, so WM will call - // KeyguardViewMediator's unocclude animation runner when the - // activity is exited. - if ( - keyguardStateController.isShowing && - !keyguardStateController.isKeyguardGoingAway - ) { - Log.d(TAG, "Setting occluded = true in #startActivity.") - keyguardViewMediatorLazy - .get() - .setOccluded(true /* isOccluded */, true /* animate */) - } - } - - override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { - // Set mIsLaunchingActivityOverLockscreen to false before actually - // finishing the animation so that we can assume that - // mIsLaunchingActivityOverLockscreen being true means that we will - // collapse the shade (or at least run the post collapse runnables) - // later on. - centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) - delegate?.onLaunchAnimationEnd(isExpandingFullyAbove) - } - - override fun onLaunchAnimationCancelled( - newKeyguardOccludedState: Boolean? - ) { - if (newKeyguardOccludedState != null) { - keyguardViewMediatorLazy - .get() - .setOccluded(newKeyguardOccludedState, false /* animate */) - } - - // Set mIsLaunchingActivityOverLockscreen to false before actually - // finishing the animation so that we can assume that - // mIsLaunchingActivityOverLockscreen being true means that we will - // collapse the shade (or at least run the // post collapse - // runnables) later on. - centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) - delegate.onLaunchAnimationCancelled(newKeyguardOccludedState) - } - } - } + controller = wrapAnimationControllerForLockscreen(delegate) } else if (dismissShade) { // The animation will take care of dismissing the shade at the end of the animation. // If we don't animate, collapse it directly. @@ -874,7 +858,7 @@ constructor( * window. * @param isLaunchForActivity whether the launch is for an activity. */ - private fun wrapAnimationController( + private fun wrapAnimationControllerForShadeOrStatusBar( animationController: ActivityLaunchAnimator.Controller?, dismissShade: Boolean, isLaunchForActivity: Boolean, @@ -909,6 +893,72 @@ constructor( return animationController } + /** + * Wraps an animation controller so that if an activity would be launched on top of the + * lockscreen, the correct flags are set for it to be occluded. + */ + private fun wrapAnimationControllerForLockscreen( + animationController: ActivityLaunchAnimator.Controller? + ): ActivityLaunchAnimator.Controller? { + return animationController?.let { + object : DelegateLaunchAnimatorController(it) { + override fun onIntentStarted(willAnimate: Boolean) { + delegate.onIntentStarted(willAnimate) + if (willAnimate) { + centralSurfaces?.setIsLaunchingActivityOverLockscreen(true) + } + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + super.onLaunchAnimationStart(isExpandingFullyAbove) + + // Double check that the keyguard is still showing and not going + // away, but if so set the keyguard occluded. Typically, WM will let + // KeyguardViewMediator know directly, but we're overriding that to + // play the custom launch animation, so we need to take care of that + // here. The unocclude animation is not overridden, so WM will call + // KeyguardViewMediator's unocclude animation runner when the + // activity is exited. + if ( + keyguardStateController.isShowing && + !keyguardStateController.isKeyguardGoingAway + ) { + Log.d(TAG, "Setting occluded = true in #startActivity.") + keyguardViewMediatorLazy + .get() + .setOccluded(true /* isOccluded */, true /* animate */) + } + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + // Set mIsLaunchingActivityOverLockscreen to false before actually + // finishing the animation so that we can assume that + // mIsLaunchingActivityOverLockscreen being true means that we will + // collapse the shade (or at least run the post collapse runnables) + // later on. + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + delegate.onLaunchAnimationEnd(isExpandingFullyAbove) + } + + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { + if (newKeyguardOccludedState != null) { + keyguardViewMediatorLazy + .get() + .setOccluded(newKeyguardOccludedState, false /* animate */) + } + + // Set mIsLaunchingActivityOverLockscreen to false before actually + // finishing the animation so that we can assume that + // mIsLaunchingActivityOverLockscreen being true means that we will + // collapse the shade (or at least run the // post collapse + // runnables) later on. + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + delegate.onLaunchAnimationCancelled(newKeyguardOccludedState) + } + } + } + } + /** Retrieves the current user handle to start the Activity. */ private fun getActivityUserHandle(intent: Intent): UserHandle { val packages: Array<String> = 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 daa4f1807625..cbe9d4b93ead 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -733,8 +733,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // Suppress all face auth errors if fingerprint can be used to authenticate if ((biometricSourceType == BiometricSourceType.FACE - && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - mSelectedUserInteractor.get().getSelectedUserId())) + && !mUpdateMonitor.isUnlockWithFingerprintPossible( + mSelectedUserInteractor.get().getSelectedUserId())) || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { mHapticsInteractor.vibrateError(); } 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 2820762e9f33..e887a483afaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -202,7 +202,6 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -297,7 +296,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; private float mTransitionToFullShadeProgress = 0f; private final NotificationListContainer mNotifListContainer; - private final NotificationExpansionRepository mNotificationExpansionRepository; private boolean mIsShortcutListSearchEnabled; private final KeyguardStateController.Callback mKeyguardStateControllerCallback = @@ -651,7 +649,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Lazy<NotificationPresenter> notificationPresenterLazy, Lazy<NotificationActivityStarter> notificationActivityStarterLazy, NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider, - NotificationExpansionRepository notificationExpansionRepository, DozeParameters dozeParameters, ScrimController scrimController, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, @@ -760,7 +757,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mPresenterLazy = notificationPresenterLazy; mNotificationActivityStarterLazy = notificationActivityStarterLazy; mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider; - mNotificationExpansionRepository = notificationExpansionRepository; mDozeServiceHost = dozeServiceHost; mPowerManager = powerManager; mDozeParameters = dozeParameters; @@ -2531,7 +2527,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && mFingerprintManager.get() != null && mFingerprintManager.get().isPowerbuttonFps() && mKeyguardUpdateMonitor - .getCachedIsUnlockWithFingerprintPossible( + .isUnlockWithFingerprintPossible( mUserTracker.getUserId()) && !touchToUnlockAnytime; if (DEBUG_WAKEUP_DELAY) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 63591d7d6f51..b0183d3fbd40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -58,7 +58,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr private var pendingUnlock: PendingUnlock? = null private val listeners = mutableListOf<OnBypassStateChangedListener>() private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback { - override fun onFaceAuthEnabledChanged() = notifyListeners() + override fun onFaceEnrolledChanged() = notifyListeners() } @IntDef( @@ -98,7 +98,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr FACE_UNLOCK_BYPASS_NEVER -> false else -> field } - return enabled && mKeyguardStateController.isFaceAuthEnabled && + return enabled && mKeyguardStateController.isFaceEnrolled && isPostureAllowedForFaceAuth() } private set(value) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index 109e77e92b7b..332984413dde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -117,9 +117,7 @@ class KeyguardLiftController @Inject constructor( val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible && !statusBarStateController.isDozing - val userId = selectedUserInteractor.getSelectedUserId() - val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId) - val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled + val shouldListen = (onKeyguard || bouncerVisible) && keyguardUpdateMonitor.isFaceEnrolled if (shouldListen != isListening) { isListening = shouldListen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 45186b019f59..3921e69501d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -83,6 +83,8 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -93,8 +95,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import kotlin.Unit; - /** * Contains the collapsed status bar and handles hiding/showing based on disable flags * and keyguard state. Also manages lifecycle to make sure the views it contains are being @@ -594,7 +594,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // Hide notifications if the disable flag is set or we have an ongoing call. if (disableNotifications || hasOngoingCall) { - hideNotificationIconArea(animate); + hideNotificationIconArea(animate && !hasOngoingCall); } else { showNotificationIconArea(animate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 8929e024c00d..52133ee5b7cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -130,7 +130,7 @@ public interface KeyguardStateController extends CallbackController<Callback> { /** * If there are faces enrolled and user enabled face auth on keyguard. */ - default boolean isFaceAuthEnabled() { + default boolean isFaceEnrolled() { return false; } @@ -265,9 +265,9 @@ public interface KeyguardStateController extends CallbackController<Callback> { /** * Triggered when face auth becomes available or unavailable. Value should be queried with - * {@link KeyguardStateController#isFaceAuthEnabled()}. + * {@link KeyguardStateController#isFaceEnrolled()}. */ - default void onFaceAuthEnabledChanged() {} + default void onFaceEnrolledChanged() {} /** * Triggered when the notification panel is starting or has finished diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index c62451813699..8cc7e7d21fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -85,7 +85,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private boolean mTrustManaged; private boolean mTrusted; private boolean mDebugUnlocked = false; - private boolean mFaceAuthEnabled; + private boolean mFaceEnrolled; private float mDismissAmount = 0f; private boolean mDismissingFromTouch = false; @@ -216,7 +216,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private void notifyKeyguardFaceAuthEnabledChanged() { // Copy the list to allow removal during callback. - new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged); + new ArrayList<>(mCallbacks).forEach(Callback::onFaceEnrolledChanged); } private void notifyUnlockedChanged() { @@ -260,16 +260,16 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked); boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user); boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user); - boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user); + boolean faceEnrolled = mKeyguardUpdateMonitor.isFaceEnrolled(user); boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen || trustManaged != mTrustManaged || mTrusted != trusted - || mFaceAuthEnabled != faceAuthEnabled; + || mFaceEnrolled != faceEnrolled; if (changed || updateAlways) { mSecure = secure; mCanDismissLockScreen = canDismissLockScreen; mTrusted = trusted; mTrustManaged = trustManaged; - mFaceAuthEnabled = faceAuthEnabled; + mFaceEnrolled = faceEnrolled; mLogger.logKeyguardStateUpdate( mSecure, mCanDismissLockScreen, mTrusted, mTrustManaged); notifyUnlockedChanged(); @@ -290,8 +290,8 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public boolean isFaceAuthEnabled() { - return mFaceAuthEnabled; + public boolean isFaceEnrolled() { + return mFaceEnrolled; } @Override @@ -416,7 +416,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum pw.println(" mTrustManaged: " + mTrustManaged); pw.println(" mTrusted: " + mTrusted); pw.println(" mDebugUnlocked: " + mDebugUnlocked); - pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled); + pw.println(" mFaceEnrolled: " + mFaceEnrolled); pw.println(" isKeyguardFadingAway: " + isKeyguardFadingAway()); pw.println(" isKeyguardGoingAway: " + isKeyguardGoingAway()); pw.println(" isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index b31f630a4317..e429446f66cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -262,7 +262,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // GIVEN fingerprint and face are NOT enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) - `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false) // WHEN unlock intent is allowed when NO biometrics are enrolled (0) @@ -292,7 +292,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // GIVEN fingerprint and face are both enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) - `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true) // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs // are enrolled @@ -315,7 +315,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN fingerprint ONLY enrolled `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) - `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true) // THEN active unlock triggers allowed on unlock intent assertTrue( @@ -326,7 +326,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN face ONLY enrolled `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) - `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false) // THEN active unlock triggers allowed on unlock intent assertTrue( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index aabdcb7fa660..efb08876b4f3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -414,6 +414,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void setupFingerprintAuth(boolean isClass3) throws RemoteException { + when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); mFingerprintSensorProperties = List.of( @@ -2898,18 +2899,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testFaceSensorProperties() throws RemoteException { + // GIVEN no face sensor properties + when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>()); - assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( + // THEN face is not possible + assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible( mSelectedUserInteractor.getSelectedUserId())).isFalse(); + // WHEN there are face sensor properties mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - biometricsEnabledForCurrentUser(); + // THEN face is possible but face does NOT start listening immediately + assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible( + mSelectedUserInteractor.getSelectedUserId())).isTrue(); verifyFaceAuthenticateNeverCalled(); verifyFaceDetectNeverCalled(); - assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( - mSelectedUserInteractor.getSelectedUserId())).isTrue(); } @Test @@ -3167,10 +3172,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void mockCanBypassLockscreen(boolean canBypass) { - // force update the isFaceEnrolled cache: - mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( - mSelectedUserInteractor.getSelectedUserId()); - mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); when(mKeyguardBypassController.canBypass()).thenReturn(canBypass); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index b8d2bdbfa9c9..86ae51768219 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -1278,7 +1278,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float magnificationScaleLarge = 2.5f; final int initSize = Math.min(bounds.width(), bounds.height()) / 3; - final int magnificationSize = (int) (initSize * magnificationScaleLarge); + final int magnificationSize = (int) (initSize * magnificationScaleLarge) + - (int) (initSize * magnificationScaleLarge) % 2; final int expectedWindowHeight = magnificationSize; final int expectedWindowWidth = magnificationSize; 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 ec6ec63d213d..e8329409ff10 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 @@ -234,10 +234,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase { mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_X, new SpringForce() .setStiffness(stiffness) - .setDampingRatio(dampingRatio), velocity, finalPosition); + .setDampingRatio(dampingRatio), velocity, finalPosition, + /* writeToPosition = */ true); mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_Y, new SpringForce() .setStiffness(stiffness) - .setDampingRatio(dampingRatio), velocity, finalPosition); + .setDampingRatio(dampingRatio), velocity, finalPosition, + /* writeToPosition = */ true); } private void skipAnimationToEnd(DynamicAnimation animation) { 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 aed795a440b4..76094c1cf185 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 @@ -42,6 +42,10 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; 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.AndroidTestingRunner; import android.testing.TestableLooper; @@ -52,8 +56,10 @@ import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.settings.SecureSettings; @@ -98,6 +104,10 @@ public class MenuViewLayerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private IAccessibilityFloatingMenu mFloatingMenu; @@ -116,7 +126,8 @@ public class MenuViewLayerTest extends SysuiTestCase { final Rect mDisplayBounds = new Rect(); mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_HEIGHT); - mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets())); + mWindowMetrics = spy( + new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f)); doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager, @@ -221,27 +232,62 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() { + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); + final PointF beforePosition = mMenuView.getMenuPosition(); + + dispatchShowingImeInsets(); + + final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(menuBottom).isLessThan(beforePosition.y); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); final PointF beforePosition = mMenuView.getMenuPosition(); dispatchShowingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); assertThat(menuBottom).isLessThan(beforePosition.y); } @Test + @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() { + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); + final PointF beforePosition = mMenuView.getMenuPosition(); + + dispatchHidingImeInsets(); + + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { - final float menuTop = IME_TOP + 200; - mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); + final PointF beforePosition = mMenuView.getMenuPosition(); + dispatchShowingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); dispatchHidingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); - assertThat(mMenuView.getTranslationX()).isEqualTo(0); - assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y); } private void setupEnabledAccessibilityServiceList() { @@ -294,4 +340,21 @@ public class MenuViewLayerTest extends SysuiTestCase { Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom)) .build(); } + + private boolean isPositionAnimationRunning() { + return !mMenuAnimationController.mPositionAnimations.values().stream().filter( + (animation) -> animation.isRunning()).findAny().isEmpty(); + } + + private void skipPositionAnimations() { + mMenuAnimationController.mPositionAnimations.values().stream().forEach( + (animation) -> { + final SpringAnimation springAnimation = ((SpringAnimation) animation); + // The doAnimationFrame function is used for skipping animation to the end. + springAnimation.doAnimationFrame(500); + springAnimation.skipToEnd(); + springAnimation.doAnimationFrame(500); + }); + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java index 297554956ed8..c7bb0f5d3906 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java @@ -31,5 +31,6 @@ public class FlagUtils { */ public static void setFlagDefaults(SetFlagsRule setFlagsRule) { setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index f6b284fffa3d..d6aa9ac87d39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -423,7 +423,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mainHandler.setMode(FakeHandler.Mode.QUEUEING) // GIVEN bouncer should be delayed due to face auth - whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) + whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) .thenReturn(true) whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true) @@ -449,7 +449,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { // GIVEN bouncer should not be delayed because device isn't in the right posture for // face auth - whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) + whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) .thenReturn(true) whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt index 1b2fc93ddb3f..2f4fc96ebf6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt @@ -67,6 +67,7 @@ class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() { isAttachedToWindow = { isAttachedToWindow }, onLongPressDetected = onLongPressDetected, onSingleTapDetected = onSingleTapDetected, + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() } ) underTest.isLongPressHandlingEnabled = true } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index ef03fdf66fd0..6c4bb372bc3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -216,6 +216,29 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isKeyguardUnlocked() = + testScope.runTest { + whenever(keyguardStateController.isUnlocked).thenReturn(false) + val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked) + + runCurrent() + assertThat(isKeyguardUnlocked).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture()) + + whenever(keyguardStateController.isUnlocked).thenReturn(true) + captor.value.onUnlockedChanged() + runCurrent() + assertThat(isKeyguardUnlocked).isTrue() + + whenever(keyguardStateController.isUnlocked).thenReturn(false) + captor.value.onKeyguardShowingChanged() + runCurrent() + assertThat(isKeyguardUnlocked).isFalse() + } + + @Test fun isDozing() = testScope.runTest { underTest.setIsDozing(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index b32905fd3b79..27325d3f4ecf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -68,7 +68,6 @@ class KeyguardInteractorTest : SysuiTestCase() { powerInteractor = PowerInteractorFactory.create().powerInteractor, featureFlags = featureFlags, sceneContainerFlags = testUtils.sceneContainerFlags, - deviceEntryRepository = testUtils.deviceEntryRepository, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index a5d74572eb98..6cd74063b39c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -46,6 +47,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardKeyEventInteractorTest : SysuiTestCase() { @@ -128,58 +130,62 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { + fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() + verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) } @Test - fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() { + fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() + verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) } @Test - fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { + fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() { powerInteractor.setAsleepForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() + verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) } @Test - fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { + fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() + verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) + } - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() + @Test + fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { + powerInteractor.setAwakeForTest() + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + + verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) + } + + @Test + fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() { + powerInteractor.setAwakeForTest() + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + + verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) + } + + @Test + fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { + powerInteractor.setAwakeForTest() + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + + verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) } @Test @@ -249,4 +255,42 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } + + private fun verifyActionUpCollapsesTheShade(keycode: Int) { + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() + } + + private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) + } + + private fun verifyActionsDoNothing(keycode: Int) { + // action down: does nothing + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) + + // action up: doesNothing + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 50ee02609635..8cfa87d27e57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -30,8 +30,8 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection @@ -55,7 +55,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint private lateinit var rootView: KeyguardRootView @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection - @Mock private lateinit var defaultLockIconSection: DefaultLockIconSection + @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection @Mock private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection @@ -75,7 +75,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { underTest = DefaultKeyguardBlueprint( defaultIndicationAreaSection, - defaultLockIconSection, + mDefaultDeviceEntryIconSection, defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, @@ -101,14 +101,14 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { val prevBlueprint = mock(KeyguardBlueprint::class.java) val someSection = mock(KeyguardSection::class.java) whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(defaultLockIconSection).plus(someSection)) + .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection)) val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(prevBlueprint, constraintLayout) - underTest.sections.minus(defaultLockIconSection).forEach { + underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach { verify(it, never()).addViews(constraintLayout) } - verify(defaultLockIconSection).addViews(constraintLayout) + verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout) verify(someSection).removeViews(constraintLayout) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 74956377d345..5f22c7da3920 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -24,14 +24,17 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -40,35 +43,54 @@ import org.mockito.Answers import org.mockito.Mock import org.mockito.MockitoAnnotations +@ExperimentalCoroutinesApi @RunWith(JUnit4::class) @SmallTest -class DefaultLockIconSectionTest : SysuiTestCase() { +class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager @Mock private lateinit var notificationPanelView: NotificationPanelView - @Mock private lateinit var featureFlags: FeatureFlags + private lateinit var featureFlags: FakeFeatureFlags @Mock private lateinit var lockIconViewController: LockIconViewController - private lateinit var underTest: DefaultLockIconSection + @Mock private lateinit var falsingManager: FalsingManager + private lateinit var underTest: DefaultDeviceEntryIconSection @Before fun setup() { MockitoAnnotations.initMocks(this) + featureFlags = + FakeFeatureFlagsClassic().apply { + set(Flags.MIGRATE_LOCK_ICON, false) + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) + set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) + } underTest = - DefaultLockIconSection( + DefaultDeviceEntryIconSection( keyguardUpdateMonitor, authController, windowManager, context, notificationPanelView, featureFlags, - lockIconViewController + { lockIconViewController }, + { DeviceEntryIconViewModel() }, + { falsingManager }, ) } @Test - fun addViewsConditionally() { - whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(true) + fun addViewsConditionally_migrateFlagOn() { + featureFlags.set(Flags.MIGRATE_LOCK_ICON, true) + val constraintLayout = ConstraintLayout(context, null) + underTest.addViews(constraintLayout) + assertThat(constraintLayout.childCount).isGreaterThan(0) + } + + @Test + fun addViewsConditionally_migrateAndRefactorFlagsOn() { + featureFlags.set(Flags.MIGRATE_LOCK_ICON, true) + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isGreaterThan(0) @@ -76,7 +98,8 @@ class DefaultLockIconSectionTest : SysuiTestCase() { @Test fun addViewsConditionally_migrateFlagOff() { - whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(false) + featureFlags.set(Flags.MIGRATE_LOCK_ICON, false) + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isEqualTo(0) @@ -87,18 +110,18 @@ class DefaultLockIconSectionTest : SysuiTestCase() { val cs = ConstraintSet() underTest.applyConstraints(cs) - val constraint = cs.getConstraint(R.id.lock_icon_view) + val constraint = cs.getConstraint(R.id.device_entry_icon_view) assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) } @Test - fun testCenterLockIcon() { + fun testCenterIcon() { val cs = ConstraintSet() - underTest.centerLockIcon(Point(5, 6), 1F, cs) + underTest.centerIcon(Point(5, 6), 1F, cs) - val constraint = cs.getConstraint(R.id.lock_icon_view) + val constraint = cs.getConstraint(R.id.device_entry_icon_view) assertThat(constraint.layout.mWidth).isEqualTo(2) assertThat(constraint.layout.mHeight).isEqualTo(2) 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 deefab670c71..b101acf3418b 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 @@ -124,10 +124,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context, controllerFactory, lmmFactory, - mr2, + { mr2 }, muteAwaitFactory, configurationController, - localBluetoothManager, + { localBluetoothManager }, fakeFgExecutor, fakeBgExecutor, dumpster, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 43cf1b5ecc40..ae47a7bfa63d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -37,7 +37,6 @@ import android.testing.TestableLooper import android.view.IWindowManager import android.view.View import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.view.LaunchableFrameLayout @@ -48,6 +47,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.res.R import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -343,8 +343,7 @@ class CustomTileTest : SysuiTestCase() { testableLooper.processAllMessages() verify(activityStarter, never()) - .startPendingIntentDismissingKeyguard( - any(), any(), any(ActivityLaunchAnimator.Controller::class.java)) + .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable()) } @Test @@ -357,8 +356,7 @@ class CustomTileTest : SysuiTestCase() { testableLooper.processAllMessages() verify(activityStarter, never()) - .startPendingIntentDismissingKeyguard( - any(), any(), any(ActivityLaunchAnimator.Controller::class.java)) + .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable()) } @Test @@ -373,7 +371,7 @@ class CustomTileTest : SysuiTestCase() { testableLooper.processAllMessages() verify(activityStarter) - .startPendingIntentDismissingKeyguard( + .startPendingIntentMaybeDismissingKeyguard( eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 0e6e4fa659da..c7118066a276 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -373,18 +373,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { } @Test - fun isVisible() { - val underTest = utils.footerActionsViewModel() - assertThat(underTest.isVisible.value).isFalse() - - underTest.onVisibilityChangeRequested(visible = true) - assertThat(underTest.isVisible.value).isTrue() - - underTest.onVisibilityChangeRequested(visible = false) - assertThat(underTest.isVisible.value).isFalse() - } - - @Test fun alpha_inSplitShade_followsExpansion() { val underTest = utils.footerActionsViewModel() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt index a6199c25874b..2bdc154dd885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt @@ -83,7 +83,7 @@ class DisabledByPolicyInteractorTest : SysuiTestCase() { @Test fun testDisabledWhenAdminWithNoRestrictions() = testScope.runTest { - val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + val admin = EnforcedAdmin(TEST_COMPONENT_NAME, TEST_USER) whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin) whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString())) .thenReturn(false) @@ -129,11 +129,11 @@ class DisabledByPolicyInteractorTest : SysuiTestCase() { } private companion object { - const val TEST_USER = 1 + const val TEST_RESTRICTION = "test_restriction" val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls") - - val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + val TEST_USER = UserHandle(1) + val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, TEST_USER) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt index fc2b7a64957d..479e62d4d724 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt @@ -47,11 +47,12 @@ class BluetoothStateInteractorTest : SysuiTestCase() { @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter @Mock private lateinit var localBluetoothManager: LocalBluetoothManager + @Mock private lateinit var logger: BluetoothTileDialogLogger @Before fun setUp() { bluetoothStateInteractor = - BluetoothStateInteractor(localBluetoothManager, testScope.backgroundScope) + BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope) `when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter) } @@ -80,6 +81,8 @@ class BluetoothStateInteractorTest : SysuiTestCase() { bluetoothStateInteractor.isBluetoothEnabled = true verify(bluetoothAdapter).enable() + verify(logger) + .logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 8b6604048ea8..3b6bfeeb4ca2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -30,6 +30,7 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.SysuiTestCase import com.android.systemui.res.R +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -60,8 +61,12 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var logger: BluetoothTileDialogLogger + private val subtitleResId = R.string.quick_settings_bluetooth_tile_subtitle + private val fakeSystemClock = FakeSystemClock() + private lateinit var icon: Pair<Drawable, String> private lateinit var bluetoothTileDialog: BluetoothTileDialog private lateinit var deviceItem: DeviceItem @@ -73,7 +78,9 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + fakeSystemClock, uiEventLogger, + logger, mContext ) icon = Pair(drawable, DEVICE_NAME) @@ -109,7 +116,9 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + fakeSystemClock, uiEventLogger, + logger, mContext ) bluetoothTileDialog.show() @@ -138,7 +147,9 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + fakeSystemClock, uiEventLogger, + logger, mContext ) .Adapter(bluetoothTileDialogCallback) @@ -162,7 +173,9 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + fakeSystemClock, uiEventLogger, + logger, mContext ) .Adapter(bluetoothTileDialogCallback) @@ -182,7 +195,9 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + fakeSystemClock, uiEventLogger, + logger, mContext ) bluetoothTileDialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index dcda005b6109..fb5dd212ff22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -77,6 +77,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var logger: BluetoothTileDialogLogger + private lateinit var scheduler: TestCoroutineScheduler private lateinit var dispatcher: CoroutineDispatcher private lateinit var testScope: TestScope @@ -92,7 +94,9 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { bluetoothStateInteractor, dialogLaunchAnimator, activityStarter, + fakeSystemClock, uiEventLogger, + logger, testScope.backgroundScope, dispatcher, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt index 428f79cf9b1b..4c173cc2956d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.TestScope @@ -38,6 +39,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -71,6 +73,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var logger: BluetoothTileDialogLogger + + private val fakeSystemClock = FakeSystemClock() + private lateinit var interactor: DeviceItemInteractor private lateinit var dispatcher: CoroutineDispatcher @@ -87,13 +93,16 @@ class DeviceItemInteractorTest : SysuiTestCase() { audioManager, adapter, localBluetoothManager, + fakeSystemClock, uiEventLogger, + logger, testScope.backgroundScope, dispatcher ) `when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1) `when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2) + `when`(cachedDevice1.address).thenReturn("ADDRESS") `when`(cachedDevice1.device).thenReturn(device1) `when`(cachedDevice2.device).thenReturn(device2) `when`(bluetoothTileDialogRepository.cachedDevices) @@ -109,7 +118,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { ) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } @@ -124,7 +133,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { ) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } @@ -139,7 +148,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { ) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(listOf(deviceItem1)) } @@ -154,7 +163,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { ) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2)) } @@ -180,7 +189,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } @@ -203,12 +212,55 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) val latest by collectLastValue(interactor.deviceItemUpdate) - interactor.updateDeviceItems(mContext) + interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD) assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } } + @Test + fun testUpdateDeviceItemOnClick_connectedMedia_setActive() { + `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) + + interactor.updateDeviceItemOnClick(deviceItem1) + + verify(cachedDevice1).setActive() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) + } + + @Test + fun testUpdateDeviceItemOnClick_activeMedia_disconnect() { + `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + + interactor.updateDeviceItemOnClick(deviceItem1) + + verify(cachedDevice1).disconnect() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + } + + @Test + fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() { + `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + + interactor.updateDeviceItemOnClick(deviceItem1) + + verify(cachedDevice1).disconnect() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + } + + @Test + fun testUpdateDeviceItemOnClick_saved_connect() { + `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) + + interactor.updateDeviceItemOnClick(deviceItem1) + + verify(cachedDevice1).connect() + verify(logger).logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE) + } + private fun createFactory( isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean, deviceItem: DeviceItem diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index 9b85012b29a9..1a4553558e28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.viewmodel +import android.os.UserHandle import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest @@ -31,7 +32,7 @@ import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.logging.QSTileLogger -import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -77,9 +78,6 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { testScope.runTest { assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - underTest.onLifecycle(QSTileLifecycle.ALIVE) - underTest.onUserIdChanged(1) - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() underTest.state.launchIn(backgroundScope) @@ -87,20 +85,22 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty() assertThat(fakeQSTileDataInteractor.dataRequests.first()) - .isEqualTo(FakeQSTileDataInteractor.DataRequest(1)) + .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0))) } private fun createViewModel( scope: TestScope, config: QSTileConfig = TEST_QS_TILE_CONFIG, ): QSTileViewModel = - BaseQSTileViewModel( - config, - fakeQSTileUserActionInteractor, - fakeQSTileDataInteractor, - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState = - QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} + QSTileViewModelImpl( + { config }, + { fakeQSTileUserActionInteractor }, + { fakeQSTileDataInteractor }, + { + object : QSTileDataToStateMapper<Any> { + override fun map(config: QSTileConfig, data: Any): QSTileState = + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} + } }, fakeDisabledByPolicyInteractor, fakeUserRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index e1345d28dbd0..c1f2d0cc518f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -22,11 +22,11 @@ import android.os.PowerManager import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.Flags import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest @@ -45,7 +45,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations @@ -87,6 +87,11 @@ class SceneContainerStartableTest : SysuiTestCase() { powerInteractor = powerInteractor, ) + @Before + fun setUp() { + mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER) + } + @Test fun hydrateVisibility() = testScope.runTest { @@ -520,7 +525,6 @@ class SceneContainerStartableTest : SysuiTestCase() { authenticationMethod: AuthenticationMethodModel? = null, startsAwake: Boolean = true, ): MutableStateFlow<ObservableTransitionState> { - assumeTrue(Flags.SCENE_CONTAINER_ENABLED) sceneContainerFlags.enabled = true utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index f6195aa9c3f7..0bed4d0d376a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -16,7 +16,10 @@ package com.android.systemui.scene.shared.flag +import android.platform.test.flag.junit.SetFlagsRule import androidx.test.filters.SmallTest +import com.android.systemui.FakeFeatureFlagsImpl +import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -24,8 +27,8 @@ import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag import com.google.common.truth.Truth.assertThat -import org.junit.Assume.assumeTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -36,27 +39,50 @@ internal class SceneContainerFlagsTest( private val testCase: TestCase, ) : SysuiTestCase() { + @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule() + private lateinit var underTest: SceneContainerFlags @Before fun setUp() { + // TODO(b/283300105): remove this reflection setting once the hard-coded + // Flags.SCENE_CONTAINER_ENABLED is no longer needed. + val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED") + field.isAccessible = true + field.set(null, true) + val featureFlags = FakeFeatureFlagsClassic().apply { - SceneContainerFlagsImpl.flags.forEach { flag -> - when (flag) { - is ResourceBooleanFlag -> set(flag, testCase.areAllFlagsSet) - is ReleasedFlag -> set(flag, testCase.areAllFlagsSet) - is UnreleasedFlag -> set(flag, testCase.areAllFlagsSet) - else -> error("Unsupported flag type ${flag.javaClass}") + SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken -> + when (flagToken) { + is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet) + is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet) + is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet) + else -> error("Unsupported flag type ${flagToken.javaClass}") } } } - underTest = SceneContainerFlagsImpl(featureFlags, testCase.isComposeAvailable) + // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule. + val aconfigFlags = FakeFeatureFlagsImpl() + + listOf( + AconfigFlags.FLAG_SCENE_CONTAINER, + ) + .forEach { flagToken -> + setFlagsRule.enableFlags(flagToken) + aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet) + } + + underTest = + SceneContainerFlagsImpl( + featureFlagsClassic = featureFlags, + featureFlags = aconfigFlags, + isComposeAvailable = testCase.isComposeAvailable, + ) } @Test fun isEnabled() { - assumeTrue(Flags.SCENE_CONTAINER_ENABLED) assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index be82bc314277..4ba850c570c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -54,7 +54,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -185,7 +184,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { powerInteractor, featureFlags, sceneContainerFlags, - new FakeDeviceEntryRepository(), new FakeKeyguardBouncerRepository(), configurationRepository, shadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index b421e1b45477..4e3e165c83bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -79,7 +79,8 @@ import com.android.systemui.statusbar.NotificationInsetsController import com.android.systemui.statusbar.NotificationShadeDepthController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.CentralSurfaces @@ -163,7 +164,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor - private val notificationExpansionRepository = NotificationExpansionRepository() + private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository() + private val notificationLaunchAnimationInteractor = + NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository) private lateinit var fakeClock: FakeSystemClock private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> @@ -234,7 +237,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { primaryBouncerToGoneTransitionViewModel, mCommunalViewModel, mCommunalRepository, - notificationExpansionRepository, + notificationLaunchAnimationInteractor, featureFlagsClassic, fakeClock, BouncerMessageInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 9c571015f750..3d5d26ab194e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -69,7 +69,8 @@ import com.android.systemui.statusbar.NotificationInsetsController import com.android.systemui.statusbar.NotificationShadeDepthController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -226,7 +227,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { primaryBouncerToGoneTransitionViewModel, mCommunalViewModel, mCommunalRepository, - NotificationExpansionRepository(), + NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()), featureFlags, FakeSystemClock(), BouncerMessageInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 0fcfaf960737..7931e9ea5d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -39,7 +39,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlags; @@ -219,7 +218,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { powerInteractor, featureFlags, sceneContainerFlags, - new FakeDeviceEntryRepository(), new FakeKeyguardBouncerRepository(), configurationRepository, mShadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 2bcad1d27b62..dd3ac927ebdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -73,10 +73,10 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.TrustGrantFlags; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.res.R; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; +import com.android.systemui.res.R; import org.junit.Test; import org.junit.runner.RunWith; @@ -1543,7 +1543,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll private void setupFingerprintUnlockPossible(boolean possible) { when(mKeyguardUpdateMonitor - .getCachedIsUnlockWithFingerprintPossible(getCurrentUser())) + .isUnlockWithFingerprintPossible(getCurrentUser())) .thenReturn(possible); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt index 235ac5c2e9cc..605936372e7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt @@ -11,7 +11,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -44,7 +45,8 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { private lateinit var notificationTestHelper: NotificationTestHelper private lateinit var notification: ExpandableNotificationRow private lateinit var controller: NotificationLaunchAnimatorController - private val notificationExpansionRepository = NotificationExpansionRepository() + private val notificationLaunchAnimationInteractor = + NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()) private val testScope = TestScope() @@ -57,16 +59,17 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { fun setUp() { allowTestableLooperAsMainThread() notificationTestHelper = - NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) notification = notificationTestHelper.createRow() - controller = NotificationLaunchAnimatorController( - notificationExpansionRepository, + controller = + NotificationLaunchAnimatorController( + notificationLaunchAnimationInteractor, notificationListContainer, headsUpManager, notification, jankMonitor, onFinishAnimationCallback - ) + ) } private fun flagNotificationAsHun() { @@ -80,13 +83,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification)) assertFalse(notification.entry.isExpandAnimationRunning) - val isExpandAnimationRunning by testScope.collectLastValue( - notificationExpansionRepository.isExpandAnimationRunning - ) + val isExpandAnimationRunning by + testScope.collectLastValue( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning + ) assertFalse(isExpandAnimationRunning!!) - verify(headsUpManager).removeNotification( - notificationKey, true /* releaseImmediately */, true /* animate */) + verify(headsUpManager) + .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) verify(onFinishAnimationCallback).run() } @@ -97,13 +101,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification)) assertFalse(notification.entry.isExpandAnimationRunning) - val isExpandAnimationRunning by testScope.collectLastValue( - notificationExpansionRepository.isExpandAnimationRunning - ) + val isExpandAnimationRunning by + testScope.collectLastValue( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning + ) assertFalse(isExpandAnimationRunning!!) - verify(headsUpManager).removeNotification( - notificationKey, true /* releaseImmediately */, true /* animate */) + verify(headsUpManager) + .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) verify(onFinishAnimationCallback).run() } @@ -114,13 +119,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification)) assertFalse(notification.entry.isExpandAnimationRunning) - val isExpandAnimationRunning by testScope.collectLastValue( - notificationExpansionRepository.isExpandAnimationRunning - ) + val isExpandAnimationRunning by + testScope.collectLastValue( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning + ) assertFalse(isExpandAnimationRunning!!) - verify(headsUpManager).removeNotification( - notificationKey, true /* releaseImmediately */, false /* animate */) + verify(headsUpManager) + .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */) verify(onFinishAnimationCallback).run() } @@ -128,15 +134,21 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { fun testAlertingSummaryHunRemovedOnNonAlertingChildLaunch() { val GROUP_KEY = "test_group_key" - val summary = NotificationEntryBuilder().setGroup(mContext, GROUP_KEY).setId(0).apply { - modifyNotification(mContext).setSmallIcon(R.drawable.ic_person) - }.build() + val summary = + NotificationEntryBuilder() + .setGroup(mContext, GROUP_KEY) + .setId(0) + .apply { modifyNotification(mContext).setSmallIcon(R.drawable.ic_person) } + .build() assertNotSame(summary.key, notification.entry.key) notificationTestHelper.createRow(summary) - GroupEntryBuilder().setKey(GROUP_KEY).setSummary(summary).addChild(notification.entry) - .build() + GroupEntryBuilder() + .setKey(GROUP_KEY) + .setSummary(summary) + .addChild(notification.entry) + .build() assertSame(summary, notification.entry.parent?.summary) `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false) @@ -147,10 +159,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { controller.onLaunchAnimationEnd(isExpandingFullyAbove = true) - verify(headsUpManager).removeNotification( - summary.key, true /* releaseImmediately */, false /* animate */) - verify(headsUpManager, never()).removeNotification( - notification.entry.key, true /* releaseImmediately */, false /* animate */) + verify(headsUpManager) + .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */) + verify(headsUpManager, never()) + .removeNotification( + notification.entry.key, + true /* releaseImmediately */, + false /* animate */ + ) } @Test @@ -158,9 +174,10 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { controller.onIntentStarted(willAnimate = true) assertTrue(notification.entry.isExpandAnimationRunning) - val isExpandAnimationRunning by testScope.collectLastValue( - notificationExpansionRepository.isExpandAnimationRunning - ) + val isExpandAnimationRunning by + testScope.collectLastValue( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning + ) assertTrue(isExpandAnimationRunning!!) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS new file mode 100644 index 000000000000..7f5384d1dbf3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include /packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt index f28d9ab06bc7..a0faab563452 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt @@ -14,42 +14,44 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.data.repository +package com.android.systemui.statusbar.notification.domain.interactor import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test @SmallTest -class NotificationExpansionRepositoryTest : SysuiTestCase() { - private val underTest = NotificationExpansionRepository() +class NotificationLaunchAnimationInteractorTest : SysuiTestCase() { + private val repository = NotificationLaunchAnimationRepository() + private val underTest = NotificationLaunchAnimationInteractor(repository) @Test - fun setIsExpandAnimationRunning_startsAsFalse() = runTest { - val latest by collectLastValue(underTest.isExpandAnimationRunning) + fun setIsLaunchAnimationRunning_startsAsFalse() = runTest { + val latest by collectLastValue(underTest.isLaunchAnimationRunning) assertThat(latest).isFalse() } @Test - fun setIsExpandAnimationRunning_false_emitsTrue() = runTest { - val latest by collectLastValue(underTest.isExpandAnimationRunning) + fun setIsLaunchAnimationRunning_false_emitsTrue() = runTest { + val latest by collectLastValue(underTest.isLaunchAnimationRunning) - underTest.setIsExpandAnimationRunning(true) + underTest.setIsLaunchAnimationRunning(true) assertThat(latest).isTrue() } @Test - fun setIsExpandAnimationRunning_false_emitsFalse() = runTest { - val latest by collectLastValue(underTest.isExpandAnimationRunning) - underTest.setIsExpandAnimationRunning(true) + fun setIsLaunchAnimationRunning_false_emitsFalse() = runTest { + val latest by collectLastValue(underTest.isLaunchAnimationRunning) + underTest.setIsLaunchAnimationRunning(true) // WHEN the animation is no longer running - underTest.setIsExpandAnimationRunning(false) + underTest.setIsLaunchAnimationRunning(false) // THEN the flow emits false assertThat(latest).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index 68f2728c9ace..7de05add2884 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -19,11 +19,14 @@ import android.content.Intent import android.os.RemoteException import android.os.UserHandle import android.testing.AndroidTestingRunner +import android.view.View +import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.LaunchableView import com.android.systemui.assist.AssistManager import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle @@ -41,6 +44,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -49,6 +53,7 @@ import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.mock @@ -116,16 +121,51 @@ class ActivityStarterImplTest : SysuiTestCase() { @Test fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() { val pendingIntent = mock(PendingIntent::class.java) + whenever(pendingIntent.isActivity).thenReturn(true) whenever(keyguardStateController.isShowing).thenReturn(true) whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) underTest.startPendingIntentDismissingKeyguard(pendingIntent) + mainExecutor.runAllReady() verify(statusBarKeyguardViewManager) .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null)) } @Test + fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() { + val pendingIntent = mock(PendingIntent::class.java) + val parent = FrameLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) + val controller = ActivityLaunchAnimator.Controller.fromView(view) + whenever(pendingIntent.isActivity).thenReturn(true) + whenever(keyguardStateController.isShowing).thenReturn(true) + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt())) + .thenReturn(true) + + underTest.startPendingIntentMaybeDismissingKeyguard( + intent = pendingIntent, + animationController = controller, + intentSentUiThreadCallback = null, + ) + mainExecutor.runAllReady() + + verify(activityLaunchAnimator) + .startPendingIntentWithAnimation( + nullable(), + eq(true), + nullable(), + eq(true), + any(), + ) + } + + @Test fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() { val pendingIntent = mock(PendingIntent::class.java) val associatedView = mock(ExpandableNotificationRow::class.java) 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 cfd220b45f1a..164325a431a5 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 @@ -140,7 +140,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); when(mKeyguardStateController.isShowing()).thenReturn(true); when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); - when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); + when(mKeyguardStateController.isFaceEnrolled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(false); when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean())) .thenReturn(true); 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 da6158052efa..6478a3e9894b 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 @@ -159,7 +159,6 @@ import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; @@ -504,7 +503,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { (Lazy<NotificationPresenter>) () -> mNotificationPresenter, (Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter, mNotifLaunchAnimControllerProvider, - new NotificationExpansionRepository(), mDozeParameters, mScrimController, mBiometricUnlockControllerLazy, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index 6209f73f2e92..bd0dbeebd752 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -86,7 +86,7 @@ class KeyguardBypassControllerTest : SysuiTestCase() { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true) - whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) + whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) } @After @@ -158,7 +158,7 @@ class KeyguardBypassControllerTest : SysuiTestCase() { keyguardBypassController.registerOnBypassStateChangedListener(bypassListener) verify(keyguardStateController).addCallback(callback.capture()) - callback.value.onFaceAuthEnabledChanged() + callback.value.onFaceEnrolledChanged() verify(bypassListener).onBypassStateChanged(anyBoolean()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 648438997166..361df1c63ffd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -168,7 +168,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { PowerInteractorFactory.create().getPowerInteractor(), mFeatureFlags, mSceneTestUtils.getSceneContainerFlags(), - mSceneTestUtils.getDeviceEntryRepository(), new FakeKeyguardBouncerRepository(), new FakeConfigurationRepository(), new FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index beac995c893b..1e3197730626 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -86,7 +86,8 @@ import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorCon import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; +import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -222,7 +223,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { HeadsUpManager headsUpManager = mock(HeadsUpManager.class); NotificationLaunchAnimatorControllerProvider notificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( - new NotificationExpansionRepository(), + new NotificationLaunchAnimationInteractor( + new NotificationLaunchAnimationRepository()), mock(NotificationListContainer.class), headsUpManager, mJankMonitor); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 3430116f8008..d1b9b8aae70e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -24,13 +24,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; @@ -42,8 +37,6 @@ import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.View; -import android.view.ViewPropertyAnimator; -import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -281,15 +274,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); } @Test @@ -321,7 +314,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -337,7 +330,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -354,7 +347,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the shade is updated to no longer be open @@ -365,7 +358,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -379,7 +372,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -393,7 +386,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.GONE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -407,7 +400,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.GONE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the transition has finished @@ -416,7 +409,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -449,7 +442,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); - verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); } @Test @@ -505,6 +498,20 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // Ongoing call started + when(mOngoingCallController.hasOngoingCall()).thenReturn(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, true); + + // Notification area is hidden without delay + assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01); + assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + } + + @Test public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -725,18 +732,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private void setUpNotificationIconAreaController() { mMockNotificationAreaController = mock(NotificationIconAreaController.class); - mNotificationAreaInner = mock(View.class); - - when(mNotificationAreaInner.getLayoutParams()).thenReturn( - new FrameLayout.LayoutParams(100, 100)); - // We should probably start using a real view so that we don't need to mock these methods. - ViewPropertyAnimator viewPropertyAnimator = mock(ViewPropertyAnimator.class); - when(mNotificationAreaInner.animate()).thenReturn(viewPropertyAnimator); - when(viewPropertyAnimator.alpha(anyFloat())).thenReturn(viewPropertyAnimator); - when(viewPropertyAnimator.setDuration(anyLong())).thenReturn(viewPropertyAnimator); - when(viewPropertyAnimator.setInterpolator(any())).thenReturn(viewPropertyAnimator); - when(viewPropertyAnimator.setStartDelay(anyLong())).thenReturn(viewPropertyAnimator); - when(viewPropertyAnimator.withEndAction(any())).thenReturn(viewPropertyAnimator); + mNotificationAreaInner = new View(mContext); when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn( mNotificationAreaInner); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index e6b09e36cae9..5c960b6633db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -97,20 +97,20 @@ public class KeyguardStateControllerTest extends SysuiTestCase { } @Test - public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() { + public void testFaceAuthEnrolleddChanged_calledWhenFaceEnrollmentStateChanges() { KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class); - when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false); + when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(false); verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); mKeyguardStateController.addCallback(callback); - assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse(); + assertThat(mKeyguardStateController.isFaceEnrolled()).isFalse(); - when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true); + when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(true); mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged( BiometricSourceType.FACE); - assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue(); - verify(callback).onFaceAuthEnabledChanged(); + assertThat(mKeyguardStateController.isFaceEnrolled()).isTrue(); + verify(callback).onFaceEnrolledChanged(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 1bc346de1568..96db09edaf88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -57,7 +57,6 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { PowerInteractorFactory.create().powerInteractor, FakeFeatureFlagsClassic(), sceneTestUtils.sceneContainerFlags, - sceneTestUtils.deviceEntryRepository, FakeKeyguardBouncerRepository(), FakeConfigurationRepository(), FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index a42fa4129a5f..ec808c796d46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -94,7 +94,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -403,7 +402,6 @@ public class BubblesTest extends SysuiTestCase { powerInteractor, featureFlags, sceneContainerFlags, - new FakeDeviceEntryRepository(), new FakeKeyguardBouncerRepository(), configurationRepository, shadeRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index fae49b120d7d..88a88c75319c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -58,6 +58,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing + private val _isKeyguardUnlocked = MutableStateFlow(false) + override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow() + private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 82ce802179a3..d2ff9bc5f3eb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -45,7 +44,6 @@ object KeyguardInteractorFactory { sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(), repository: FakeKeyguardRepository = FakeKeyguardRepository(), commandQueue: FakeCommandQueue = FakeCommandQueue(), - deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(), bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(), shadeRepository: FakeShadeRepository = FakeShadeRepository(), @@ -57,7 +55,6 @@ object KeyguardInteractorFactory { commandQueue = commandQueue, featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, - deviceEntryRepository = deviceEntryRepository, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, @@ -67,7 +64,6 @@ object KeyguardInteractorFactory { commandQueue = commandQueue, featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, - deviceEntryRepository = deviceEntryRepository, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, @@ -87,7 +83,6 @@ object KeyguardInteractorFactory { val commandQueue: FakeCommandQueue, val featureFlags: FakeFeatureFlags, val sceneContainerFlags: SceneContainerFlags, - val deviceEntryRepository: FakeDeviceEntryRepository, val bouncerRepository: FakeKeyguardBouncerRepository, val configurationRepository: FakeConfigurationRepository, val shadeRepository: FakeShadeRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt index f62bf6014374..1efa74b0551a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.base.interactor +import android.os.UserHandle + class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { var handleResult: Boolean = false @@ -23,7 +25,7 @@ class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { DisabledByPolicyInteractor.PolicyResult.TileEnabled override suspend fun isDisabled( - userId: Int, + user: UserHandle, userRestriction: String? ): DisabledByPolicyInteractor.PolicyResult = policyResult diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 55935961466d..2b3330f3a33b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.base.interactor +import android.os.UserHandle import javax.annotation.CheckReturnValue import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -38,16 +39,16 @@ class FakeQSTileDataInteractor<T>( fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable) suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable) - override fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<T> { - mutableDataRequests.add(DataRequest(userId)) + override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> { + mutableDataRequests.add(DataRequest(user)) return triggers.flatMapLatest { dataFlow } } - override fun availability(userId: Int): Flow<Boolean> { - mutableAvailabilityRequests.add(AvailabilityRequest(userId)) + override fun availability(user: UserHandle): Flow<Boolean> { + mutableAvailabilityRequests.add(AvailabilityRequest(user)) return availabilityFlow } - data class DataRequest(val userId: Int) - data class AvailabilityRequest(val userId: Int) + data class DataRequest(val user: UserHandle) + data class AvailabilityRequest(val user: UserHandle) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index a4881bc6efec..17384351f94d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -185,7 +185,6 @@ class SceneTestUtils( commandQueue = FakeCommandQueue(), featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, - deviceEntryRepository = FakeDeviceEntryRepository(), bouncerRepository = FakeKeyguardBouncerRepository(), configurationRepository = FakeConfigurationRepository(), shadeRepository = FakeShadeRepository(), diff --git a/services/Android.bp b/services/Android.bp index 3ae9360f3c44..aca8409c284f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -238,12 +238,14 @@ filegroup { stubs_defaults { name: "services-stubs-default", installable: false, - args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" + - " --hide-annotation android.annotation.Hide" + - " --hide InternalClasses" + // com.android.* classes are okay in this interface + flags: [ + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)", + "--hide-annotation android.annotation.Hide", + "--hide InternalClasses", // com.android.* classes are okay in this interface // TODO: remove the --hide options below - " --hide DeprecationMismatch" + - " --hide HiddenTypedefConstant", + "--hide DeprecationMismatch", + "--hide HiddenTypedefConstant", + ], visibility: ["//frameworks/base:__subpackages__"], filter_packages: ["com.android."], } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 87f9cf10f824..d575102bd5e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -64,7 +64,6 @@ import android.app.PendingIntent; import android.app.RemoteAction; import android.app.admin.DevicePolicyManager; import android.appwidget.AppWidgetManagerInternal; -import android.companion.virtual.VirtualDeviceManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -1071,18 +1070,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler, Context.RECEIVER_EXPORTED); - if (android.companion.virtual.flags.Flags.vdmPublicApis()) { - VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class); - if (vdm != null) { - vdm.registerVirtualDeviceListener(mContext.getMainExecutor(), - new VirtualDeviceManager.VirtualDeviceListener() { - @Override - public void onVirtualDeviceClosed(int deviceId) { - mProxyManager.clearConnections(deviceId); - } - }); - } - } else { + if (!android.companion.virtual.flags.Flags.vdmPublicApis()) { final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index ed77476a2295..2032a5021cd9 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -101,6 +101,8 @@ public class ProxyManager { private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener; + private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener; + /** * Callbacks into AccessibilityManagerService. */ @@ -189,6 +191,9 @@ public class ProxyManager { } } } + if (mProxyA11yServiceConnections.size() == 1) { + registerVirtualDeviceListener(); + } } // If the client dies, make sure to remove the connection. @@ -210,6 +215,31 @@ public class ProxyManager { connection.initializeServiceInterface(client); } + private void registerVirtualDeviceListener() { + VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class); + if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) { + return; + } + if (mVirtualDeviceListener == null) { + mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() { + @Override + public void onVirtualDeviceClosed(int deviceId) { + clearConnections(deviceId); + } + }; + } + + vdm.registerVirtualDeviceListener(mContext.getMainExecutor(), mVirtualDeviceListener); + } + + private void unregisterVirtualDeviceListener() { + VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class); + if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) { + return; + } + vdm.unregisterVirtualDeviceListener(mVirtualDeviceListener); + } + /** * Unregister the proxy based on display id. */ @@ -258,6 +288,9 @@ public class ProxyManager { deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId(); mProxyA11yServiceConnections.remove(displayId); removedFromConnections = true; + if (mProxyA11yServiceConnections.size() == 0) { + unregisterVirtualDeviceListener(); + } } } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 423b85f9305f..4688658bf1c3 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -56,7 +56,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; - private FillServiceCallbacks mCallbacks; + private final FillServiceCallbacks mCallbacks; private final Object mLock = new Object(); private CompletableFuture<FillResponse> mPendingFillRequest; private int mPendingFillRequestId = INVALID_REQUEST_ID; @@ -128,12 +128,9 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { */ public int cancelCurrentRequest() { synchronized (mLock) { - int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false) + return mPendingFillRequest != null && mPendingFillRequest.cancel(false) ? mPendingFillRequestId : INVALID_REQUEST_ID; - mPendingFillRequest = null; - mPendingFillRequestId = INVALID_REQUEST_ID; - return canceledRequestId; } } @@ -187,10 +184,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { mPendingFillRequest = null; mPendingFillRequestId = INVALID_REQUEST_ID; } - if (mCallbacks == null) { - Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); - return; - } if (err == null) { mCallbacks.onFillRequestSuccess(request.getId(), res, mComponentName.getPackageName(), request.getFlags()); @@ -227,10 +220,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return save; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS) .whenComplete((res, err) -> Handler.getMain().post(() -> { - if (mCallbacks == null) { - Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); - return; - } if (err == null) { mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res); } else { @@ -245,8 +234,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } public void destroy() { - cancelCurrentRequest(); unbind(); - mCallbacks = null; } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 4dca5a5c807b..961e9d3dcfff 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -197,6 +197,7 @@ java_library_static { "android.hardware.power.stats-V2-java", "android.hidl.manager-V1.2-java", "cbor-java", + "dropbox_flags_lib", "icu4j_calendar_astronomer", "android.security.aaid_aidl-java", "netd-client", @@ -205,6 +206,7 @@ java_library_static { "com.android.sysprop.watchdog", "ImmutabilityAnnotation", "securebox", + "android.content.pm.flags-aconfig-java", "apache-commons-math", "backstage_power_flags_lib", "notification_flags_lib", diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 55069b779a37..f82a6aabfefb 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -16,10 +16,14 @@ package com.android.server; +import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -30,6 +34,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.BundleMerger; import android.os.Debug; @@ -66,6 +71,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ObjectUtils; import com.android.server.DropBoxManagerInternal.EntrySource; +import com.android.server.feature.flags.Flags; import libcore.io.IoUtils; @@ -89,6 +95,13 @@ import java.util.zip.GZIPOutputStream; * Clients use {@link DropBoxManager} to access this service. */ public final class DropBoxManagerService extends SystemService { + /** + * For Android U and earlier versions, apps can continue to use the READ_LOGS permission, + * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L; private static final String TAG = "DropBoxManagerService"; private static final int DEFAULT_AGE_SECONDS = 3 * 86400; private static final int DEFAULT_MAX_FILES = 1000; @@ -109,7 +122,6 @@ public final class DropBoxManagerService extends SystemService { // Tags that we should drop by default. private static final List<String> DISABLED_BY_DEFAULT_TAGS = List.of("data_app_wtf", "system_app_wtf", "system_server_wtf"); - // TODO: This implementation currently uses one file per entry, which is // inefficient for smallish entries -- consider using a single queue file // per tag (or even globally) instead. @@ -291,8 +303,21 @@ public final class DropBoxManagerService extends SystemService { if (!DropBoxManagerService.this.mBooted) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_LOGS, options); + if (Flags.enableReadDropboxPermission()) { + BroadcastOptions unbundledOptions = (options == null) + ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options); + + unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle()); + + unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.READ_LOGS, unbundledOptions.toBundle()); + } else { + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.READ_LOGS, options); + } } private Intent createIntent(String tag, long time) { @@ -572,9 +597,16 @@ public final class DropBoxManagerService extends SystemService { return true; } + + String permission = Manifest.permission.READ_LOGS; + if (Flags.enableReadDropboxPermission() + && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) { + permission = Manifest.permission.READ_DROPBOX_DATA; + } + // Callers always need this permission - getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.READ_LOGS, TAG); + getContext().enforceCallingOrSelfPermission(permission, TAG); + // Callers also need the ability to read usage statistics switch (getContext().getSystemService(AppOpsManager.class).noteOp( diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 3af0e8c54cd2..15fc2dc15d02 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3007,7 +3007,7 @@ class StorageManagerService extends IStorageManager.Stub // We need all the users unlocked to move their primary storage users = mContext.getSystemService(UserManager.class).getUsers(); for (UserInfo user : users) { - if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) { + if (StorageManager.isFileEncrypted() && !isCeStorageUnlocked(user.id)) { Slog.w(TAG, "Failing move due to locked user " + user.id); onMoveStatusLocked(PackageManager.MOVE_FAILED_LOCKED_USER); return; @@ -3231,12 +3231,12 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void createUserKey(int userId, int serialNumber, boolean ephemeral) { + public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) { - super.createUserKey_enforcePermission(); + super.createUserStorageKeys_enforcePermission(); try { - mVold.createUserKey(userId, serialNumber, ephemeral); + mVold.createUserStorageKeys(userId, serialNumber, ephemeral); // Since the user's CE key was just created, the user's CE storage is now unlocked. synchronized (mLock) { mCeUnlockedUsers.append(userId); @@ -3248,12 +3248,12 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void destroyUserKey(int userId) { + public void destroyUserStorageKeys(int userId) { - super.destroyUserKey_enforcePermission(); + super.destroyUserStorageKeys_enforcePermission(); try { - mVold.destroyUserKey(userId); + mVold.destroyUserStorageKeys(userId); // Since the user's CE key was just destroyed, the user's CE storage is now locked. synchronized (mLock) { mCeUnlockedUsers.remove(userId); @@ -3266,21 +3266,22 @@ class StorageManagerService extends IStorageManager.Stub /* Only for use by LockSettingsService */ @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException { - super.setUserKeyProtection_enforcePermission(); + public void setCeStorageProtection(@UserIdInt int userId, byte[] secret) + throws RemoteException { + super.setCeStorageProtection_enforcePermission(); - mVold.setUserKeyProtection(userId, HexDump.toHexString(secret)); + mVold.setCeStorageProtection(userId, HexDump.toHexString(secret)); } /* Only for use by LockSettingsService */ @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret) - throws RemoteException { - super.unlockUserKey_enforcePermission(); + public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret) + throws RemoteException { + super.unlockCeStorage_enforcePermission(); if (StorageManager.isFileEncrypted()) { - mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret)); + mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret)); } synchronized (mLock) { mCeUnlockedUsers.append(userId); @@ -3289,23 +3290,22 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void lockUserKey(int userId) { - // Do not lock user 0 data for headless system user - super.lockUserKey_enforcePermission(); + public void lockCeStorage(int userId) { + super.lockCeStorage_enforcePermission(); + // Never lock the CE storage of a headless system user. if (userId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) { throw new IllegalArgumentException("Headless system user data cannot be locked.."); } - - if (!isUserKeyUnlocked(userId)) { + if (!isCeStorageUnlocked(userId)) { Slog.d(TAG, "User " + userId + "'s CE storage is already locked"); return; } try { - mVold.lockUserKey(userId); + mVold.lockCeStorage(userId); } catch (Exception e) { Slog.wtf(TAG, e); return; @@ -3317,7 +3317,7 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public boolean isUserKeyUnlocked(int userId) { + public boolean isCeStorageUnlocked(int userId) { synchronized (mLock) { return mCeUnlockedUsers.contains(userId); } @@ -3719,8 +3719,8 @@ class StorageManagerService extends IStorageManager.Stub final int userId = UserHandle.getUserId(callingUid); final String propertyName = "sys.user." + userId + ".ce_available"; - // Ignore requests to create directories while storage is locked - if (!isUserKeyUnlocked(userId)) { + // Ignore requests to create directories while CE storage is locked + if (!isCeStorageUnlocked(userId)) { throw new IllegalStateException("Failed to prepare " + appPath); } @@ -3846,15 +3846,15 @@ class StorageManagerService extends IStorageManager.Stub final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM); final boolean userIsDemo; - final boolean userKeyUnlocked; final boolean storagePermission; + final boolean ceStorageUnlocked; final long token = Binder.clearCallingIdentity(); try { userIsDemo = LocalServices.getService(UserManagerInternal.class) .getUserInfo(userId).isDemo(); storagePermission = mStorageManagerInternal.hasExternalStorage(callingUid, callingPackage); - userKeyUnlocked = isUserKeyUnlocked(userId); + ceStorageUnlocked = isCeStorageUnlocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -3914,7 +3914,7 @@ class StorageManagerService extends IStorageManager.Stub } else if (!systemUserUnlocked) { reportUnmounted = true; Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked"); - } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) { + } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !ceStorageUnlocked) { reportUnmounted = true; Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked"); } else if (!storagePermission && !realState) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ef67cbe5024b..210c18d78c23 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2299,7 +2299,7 @@ public class ActivityManagerService extends IActivityManager.Stub return; } // TODO(b/148767783): should we check all profiles under user0? - UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId), + UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId), BackgroundThread.getExecutor()); } @@ -4648,7 +4648,7 @@ public class ActivityManagerService extends IActivityManager.Stub // We carefully use the same state that PackageManager uses for // filtering, since we use this flag to decide if we need to install // providers when user is unlocked later - app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId)); + app.setUnlocked(StorageManager.isCeStorageUnlocked(app.userId)); } boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); @@ -9718,9 +9718,29 @@ public class ActivityManagerService extends IActivityManager.Stub public ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons( String packageName, int maxNum, int userId) { enforceNotIsolatedCaller("getHistoricalProcessStartReasons"); + // For the simplification, we don't support USER_ALL nor USER_CURRENT here. + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { + throw new IllegalArgumentException("Unsupported userId"); + } - final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>(); + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL, + "getHistoricalProcessStartReasons", null); + final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>(); + if (!TextUtils.isEmpty(packageName)) { + final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, + "getHistoricalProcessStartReasons"); + if (uid != INVALID_UID) { + mProcessList.mAppStartInfoTracker.getStartInfo( + packageName, userId, callingPid, maxNum, results); + } + } else { + // If no package name is given, use the caller's uid as the filter uid. + mProcessList.mAppStartInfoTracker.getStartInfo( + packageName, callingUid, callingPid, maxNum, results); + } return new ParceledListSlice<ApplicationStartInfo>(results); } @@ -9729,6 +9749,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void setApplicationStartInfoCompleteListener( IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener"); + + // For the simplification, we don't support USER_ALL nor USER_CURRENT here. + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { + throw new IllegalArgumentException("Unsupported userId"); + } + + final int callingUid = Binder.getCallingUid(); + mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid); } @@ -9742,6 +9770,7 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); + mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true); } @Override @@ -10043,6 +10072,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); + mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); + pw.println("-------------------------------------------------------------------------------"); mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage); } if (dumpPackage == null) { @@ -10439,6 +10470,12 @@ public class ActivityManagerService extends IActivityManager.Stub LockGuard.dump(fd, pw, args); } else if ("users".equals(cmd)) { dumpUsers(pw); + } else if ("start-info".equals(cmd)) { + if (opti < args.length) { + dumpPackage = args[opti]; + opti++; + } + mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); } else if ("exit-info".equals(cmd)) { if (opti < args.length) { dumpPackage = args[opti]; @@ -15897,7 +15934,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { sdkSandboxInfo = sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation( - sdkSandboxClientAppInfo, userId, isSdkInSandbox); + sdkSandboxClientAppInfo, isSdkInSandbox); } catch (NameNotFoundException e) { reportStartInstrumentationFailureLocked( watcher, className, "Can't find SdkSandbox package"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index a057f32386f6..69bf612f3e54 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -272,6 +272,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetWatchHeap(pw); case "clear-watch-heap": return runClearWatchHeap(pw); + case "clear-start-info": + return runClearStartInfo(pw); case "clear-exit-info": return runClearExitInfo(pw); case "bug-report": @@ -1339,6 +1341,31 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runClearStartInfo(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + "runClearStartInfo()"); + String opt; + int userId = UserHandle.USER_CURRENT; + String packageName = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + packageName = opt; + } + } + if (userId == UserHandle.USER_CURRENT) { + UserInfo user = mInterface.getCurrentUser(); + if (user == null) { + return -1; + } + userId = user.id; + } + mInternal.mProcessList.mAppStartInfoTracker + .clearHistoryProcessStartInfo(packageName, userId); + return 0; + } + int runClearExitInfo(PrintWriter pw) throws RemoteException { mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "runClearExitInfo()"); @@ -4090,6 +4117,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" s[ervices] [COMP_SPEC ...]: service state"); pw.println(" allowed-associations: current package association restrictions"); pw.println(" as[sociations]: tracked app associations"); + pw.println(" start-info [PACKAGE_NAME]: historical process start information"); pw.println(" exit-info [PACKAGE_NAME]: historical process exit information"); pw.println(" lmk: stats on low memory killer"); pw.println(" lru: raw LRU process list"); @@ -4265,6 +4293,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" above <HEAP-LIMIT> then a heap dump is collected for the user to report."); pw.println(" clear-watch-heap"); pw.println(" Clear the previously set-watch-heap."); + pw.println(" clear-start-info [--user <USER_ID> | all | current] [package]"); + pw.println(" Clear the process start-info for given package"); pw.println(" clear-exit-info [--user <USER_ID> | all | current] [package]"); pw.println(" Clear the process exit-info for given package"); pw.println(" bug-report [--progress | --telephony]"); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java new file mode 100644 index 000000000000..edca74fae0e4 --- /dev/null +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -0,0 +1,989 @@ +/* + * 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.server.am; + +import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.app.ActivityOptions; +import android.app.ApplicationStartInfo; +import android.app.Flags; +import android.app.IApplicationStartInfoCompleteListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.icu.text.SimpleDateFormat; +import android.os.Binder; +import android.os.FileUtils; +import android.os.Handler; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ProcessMap; +import com.android.server.IoThread; +import com.android.server.ServiceThread; +import com.android.server.SystemServiceManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; + +/** A class to manage all the {@link android.app.ApplicationStartInfo} records. */ +public final class AppStartInfoTracker { + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM; + + /** Interval of persisting the app start info to persistent storage. */ + private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); + + /** These are actions that the forEach* should take after each iteration */ + private static final int FOREACH_ACTION_NONE = 0; + private static final int FOREACH_ACTION_REMOVE_ITEM = 1; + private static final int FOREACH_ACTION_STOP_ITERATION = 2; + + private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; + + @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore"; + + @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo"; + + private final Object mLock = new Object(); + + private boolean mEnabled = false; + + /** Initialized in {@link #init} and read-only after that. */ + private ActivityManagerService mService; + + /** Initialized in {@link #init} and read-only after that. */ + private Handler mHandler; + + /** The task to persist app process start info */ + @GuardedBy("mLock") + private Runnable mAppStartInfoPersistTask = null; + + /** + * Last time(in ms) since epoch that the app start info was persisted into persistent storage. + */ + @GuardedBy("mLock") + private long mLastAppStartInfoPersistTimestamp = 0L; + + /** + * Retention policy: keep up to X historical start info per package. + * + * <p>Initialized in {@link #init} and read-only after that. No lock is needed. + */ + private int mAppStartInfoHistoryListSize; + + @GuardedBy("mLock") + private final ProcessMap<AppStartInfoContainer> mData; + + /** UID as key. */ + @GuardedBy("mLock") + private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks; + + /** + * Whether or not we've loaded the historical app process start info from persistent storage. + */ + @VisibleForTesting AtomicBoolean mAppStartInfoLoaded = new AtomicBoolean(); + + /** Temporary list being used to filter/sort intermediate results in {@link #getStartInfo}. */ + @GuardedBy("mLock") + final ArrayList<ApplicationStartInfo> mTmpStartInfoList = new ArrayList<>(); + + /** + * The path to the directory which includes the historical proc start info file as specified in + * {@link #mProcStartInfoFile}. + */ + @VisibleForTesting File mProcStartStoreDir; + + /** The path to the historical proc start info file, persisted in the storage. */ + @VisibleForTesting File mProcStartInfoFile; + + AppStartInfoTracker() { + mCallbacks = new SparseArray<>(); + mData = new ProcessMap<AppStartInfoContainer>(); + } + + void init(ActivityManagerService service) { + mService = service; + + ServiceThread thread = + new ServiceThread(TAG + ":handler", THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mHandler = new Handler(thread.getLooper()); + + mProcStartStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_START_STORE_DIR); + if (!FileUtils.createDir(mProcStartStoreDir)) { + Slog.e(TAG, "Unable to create " + mProcStartStoreDir); + return; + } + mProcStartInfoFile = new File(mProcStartStoreDir, APP_START_INFO_FILE); + + mAppStartInfoHistoryListSize = APP_START_INFO_HISTORY_LIST_SIZE; + } + + void onSystemReady() { + mEnabled = Flags.appStartInfo(); + if (!mEnabled) { + return; + } + + registerForUserRemoval(); + registerForPackageRemoval(); + IoThread.getHandler().post(() -> { + loadExistingProcessStartInfo(); + }); + } + + void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord, + ProcessRecord app) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime()); + start.setStartType(ApplicationStartInfo.START_TYPE_COLD); + start.setReason(ApplicationStartInfo.START_REASON_OTHER); + addStartInfoLocked(start); + } + } + + public void handleProcessActivityWarmOrHotStarted(long startTimeNs, + ActivityOptions activityOptions, Intent intent) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setIntent(intent); + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + if (activityOptions != null) { + start.setProcessName(activityOptions.getPackageName()); + } + start.setStartType(ApplicationStartInfo.START_TYPE_WARM); + if (intent != null && intent.getCategories() != null + && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + } else { + start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY); + } + addStartInfoLocked(start); + } + } + + public void handleProcessActivityStartedFromRecents(long startTimeNs, + ActivityOptions activityOptions) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + if (activityOptions != null) { + start.setIntent(activityOptions.getResultData()); + start.setProcessName(activityOptions.getPackageName()); + } + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS); + start.setStartType(ApplicationStartInfo.START_TYPE_WARM); + addStartInfoLocked(start); + } + } + + public void handleProcessServiceStart(long startTimeNs, ProcessRecord app, + ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(serviceRecord.permission != null + && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE") + ? ApplicationStartInfo.START_REASON_JOB + : ApplicationStartInfo.START_REASON_SERVICE); + start.setIntent(serviceRecord.intent.getIntent()); + addStartInfoLocked(start); + } + } + + public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, + BroadcastRecord broadcast, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + if (broadcast == null) { + start.setReason(ApplicationStartInfo.START_REASON_BROADCAST); + } else if (broadcast.alarm) { + start.setReason(ApplicationStartInfo.START_REASON_ALARM); + } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) { + start.setReason(ApplicationStartInfo.START_REASON_PUSH); + } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) { + start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE); + } else { + start.setReason(ApplicationStartInfo.START_REASON_BROADCAST); + } + start.setIntent(broadcast != null ? broadcast.intent : null); + addStartInfoLocked(start); + } + } + + public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app, + boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER); + addStartInfoLocked(start); + } + } + + public void handleProcessBackupStart(long startTimeNs, ProcessRecord app, + BackupRecord backupRecord, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(ApplicationStartInfo.START_REASON_BACKUP); + addStartInfoLocked(start); + } + } + + private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) { + if (app == null) { + return; + } + final int definingUid = app.getHostingRecord() != null + ? app.getHostingRecord().getDefiningUid() : 0; + start.setPid(app.getPid()); + start.setRealUid(app.uid); + start.setPackageUid(app.info.uid); + start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); + start.setProcessName(app.processName); + } + + void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE); + } + + void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); + } + + void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); + } + + void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); + } + + void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(processName, uid, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); + } + + private void addTimestampToStart(ProcessRecord app, long timeNs, int key) { + addTimestampToStart(app.processName, app.uid, timeNs, key); + } + + private void addTimestampToStart(String processName, int uid, long timeNs, int key) { + synchronized (mLock) { + AppStartInfoContainer container = mData.get(processName, uid); + if (container == null) { + // Record was not created, discard new data. + return; + } + container.addTimestampToStartLocked(key, timeNs); + } + } + + @GuardedBy("mLock") + private ApplicationStartInfo addStartInfoLocked(ApplicationStartInfo raw) { + if (!mAppStartInfoLoaded.get()) { + //records added before initial load from storage will be lost. + Slog.w(TAG, "Skipping saving the start info due to ongoing loading from storage"); + return null; + } + + final ApplicationStartInfo info = new ApplicationStartInfo(raw); + + AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid()); + if (container == null) { + container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); + container.mUid = info.getRealUid(); + mData.put(raw.getProcessName(), raw.getRealUid(), container); + } + container.addStartInfoLocked(info); + + schedulePersistProcessStartInfo(false); + + return info; + } + + /** + * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for + * completeness and triggers callback if a callback has been registered and the object + * is complete. + */ + private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) { + synchronized (mLock) { + if (startInfo.getStartupState() + == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { + ApplicationStartInfoCompleteCallback callback = + mCallbacks.get(startInfo.getRealUid()); + if (callback != null) { + callback.onApplicationStartInfoComplete(startInfo); + } + } + } + } + + void getStartInfo(String packageName, int filterUid, int filterPid, + int maxNum, ArrayList<ApplicationStartInfo> results) { + if (!mEnabled) { + return; + } + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + boolean emptyPackageName = TextUtils.isEmpty(packageName); + if (!emptyPackageName) { + // fast path + AppStartInfoContainer container = mData.get(packageName, filterUid); + if (container != null) { + container.getStartInfoLocked(filterPid, maxNum, results); + } + } else { + // slow path + final ArrayList<ApplicationStartInfo> list = mTmpStartInfoList; + list.clear(); + // get all packages + forEachPackageLocked( + (name, records) -> { + AppStartInfoContainer container = records.get(filterUid); + if (container != null) { + list.addAll(container.mInfos); + } + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + + Collections.sort( + list, (a, b) -> + Long.compare(getStartTimestamp(b), getStartTimestamp(a))); + int size = list.size(); + if (maxNum > 0) { + size = Math.min(size, maxNum); + } + for (int i = 0; i < size; i++) { + results.add(list.get(i)); + } + list.clear(); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + final class ApplicationStartInfoCompleteCallback implements DeathRecipient { + private final int mUid; + private final IApplicationStartInfoCompleteListener mCallback; + + ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback, + int uid) { + mCallback = callback; + mUid = uid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + void onApplicationStartInfoComplete(ApplicationStartInfo startInfo) { + try { + mCallback.onApplicationStartInfoComplete(startInfo); + } catch (RemoteException e) { + /*ignored*/ + } + clearStartInfoCompleteListener(mUid, true); + } + + void unlinkToDeath() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + clearStartInfoCompleteListener(mUid, false); + } + } + + void addStartInfoCompleteListener( + final IApplicationStartInfoCompleteListener listener, final int uid) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid)); + } + } + + void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (unlinkDeathRecipient) { + ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid); + if (callback != null) { + callback.unlinkToDeath(); + } + } + mCallbacks.remove(uid); + } + } + + @GuardedBy("mLock") + private void forEachPackageLocked( + BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) { + if (callback != null) { + ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); + for (int i = map.size() - 1; i >= 0; i--) { + switch (callback.apply(map.keyAt(i), map.valueAt(i))) { + case FOREACH_ACTION_REMOVE_ITEM: + map.removeAt(i); + break; + case FOREACH_ACTION_STOP_ITERATION: + i = 0; + break; + case FOREACH_ACTION_NONE: + default: + break; + } + } + } + } + + @GuardedBy("mLock") + private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) { + ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); + SparseArray<AppStartInfoContainer> array = map.get(packageName); + if (array == null) { + return; + } + if (userId == UserHandle.USER_ALL) { + mData.getMap().remove(packageName); + } else { + for (int i = array.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(array.keyAt(i)) == userId) { + array.removeAt(i); + break; + } + } + if (array.size() == 0) { + map.remove(packageName); + } + } + } + + @GuardedBy("mLock") + private void removeByUserIdLocked(final int userId) { + if (userId == UserHandle.USER_ALL) { + mData.getMap().clear(); + return; + } + forEachPackageLocked( + (packageName, records) -> { + for (int i = records.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(records.keyAt(i)) == userId) { + records.removeAt(i); + break; + } + } + return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE; + }); + } + + @VisibleForTesting + void onUserRemoved(int userId) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + removeByUserIdLocked(userId); + schedulePersistProcessStartInfo(true); + } + } + + @VisibleForTesting + void onPackageRemoved(String packageName, int uid, boolean allUsers) { + if (!mEnabled) { + return; + } + if (packageName != null) { + final boolean removeUid = + TextUtils.isEmpty(mService.mPackageManagerInt.getNameForUid(uid)); + synchronized (mLock) { + removePackageLocked( + packageName, + uid, + removeUid, + allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid)); + schedulePersistProcessStartInfo(true); + } + } + } + + private void registerForUserRemoval() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + mService.mContext.registerReceiverForAllUsers( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId < 1) return; + onUserRemoved(userId); + } + }, + filter, + null, + mHandler); + } + + private void registerForPackageRemoval() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mService.mContext.registerReceiverForAllUsers( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (replacing) { + return; + } + int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); + boolean allUsers = + intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false); + onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers); + } + }, + filter, + null, + mHandler); + } + + /** + * Load the existing {@link android.app.ApplicationStartInfo} records from persistent storage. + */ + @VisibleForTesting + void loadExistingProcessStartInfo() { + if (!mEnabled) { + return; + } + if (!mProcStartInfoFile.canRead()) { + // If file can't be read, mark complete so we can begin accepting new records. + mAppStartInfoLoaded.set(true); + return; + } + + FileInputStream fin = null; + try { + AtomicFile af = new AtomicFile(mProcStartInfoFile); + fin = af.openRead(); + ProtoInputStream proto = new ProtoInputStream(fin); + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.LAST_UPDATE_TIMESTAMP: + synchronized (mLock) { + mLastAppStartInfoPersistTimestamp = + proto.readLong(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP); + } + break; + case (int) AppsStartInfoProto.PACKAGES: + loadPackagesFromProto(proto, next); + break; + } + } + } catch (IOException | IllegalArgumentException | WireTypeMismatchException + | ClassNotFoundException e) { + Slog.w(TAG, "Error in loading historical app start info from persistent storage: " + e); + } finally { + if (fin != null) { + try { + fin.close(); + } catch (IOException e) { + } + } + } + mAppStartInfoLoaded.set(true); + } + + private void loadPackagesFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + long token = proto.start(fieldId); + String pkgName = ""; + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.Package.PACKAGE_NAME: + pkgName = proto.readString(AppsStartInfoProto.Package.PACKAGE_NAME); + break; + case (int) AppsStartInfoProto.Package.USERS: + AppStartInfoContainer container = + new AppStartInfoContainer(mAppStartInfoHistoryListSize); + int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS); + synchronized (mLock) { + mData.put(pkgName, uid, container); + } + break; + } + } + proto.end(token); + } + + /** Persist the existing {@link android.app.ApplicationStartInfo} records to storage. */ + @VisibleForTesting + void persistProcessStartInfo() { + if (!mEnabled) { + return; + } + AtomicFile af = new AtomicFile(mProcStartInfoFile); + FileOutputStream out = null; + long now = System.currentTimeMillis(); + try { + out = af.startWrite(); + ProtoOutputStream proto = new ProtoOutputStream(out); + proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now); + synchronized (mLock) { + forEachPackageLocked( + (packageName, records) -> { + long token = proto.start(AppsStartInfoProto.PACKAGES); + proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName); + int uidArraySize = records.size(); + for (int j = 0; j < uidArraySize; j++) { + try { + records.valueAt(j) + .writeToProto(proto, AppsStartInfoProto.Package.USERS); + } catch (IOException e) { + Slog.w(TAG, "Unable to write app start info into persistent" + + "storage: " + e); + } + } + proto.end(token); + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + mLastAppStartInfoPersistTimestamp = now; + } + proto.flush(); + af.finishWrite(out); + } catch (IOException e) { + Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e); + af.failWrite(out); + } + synchronized (mLock) { + mAppStartInfoPersistTask = null; + } + } + + /** + * Schedule a task to persist the {@link android.app.ApplicationStartInfo} records to storage. + */ + @VisibleForTesting + void schedulePersistProcessStartInfo(boolean immediately) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (mAppStartInfoPersistTask == null || immediately) { + if (mAppStartInfoPersistTask != null) { + IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); + } + mAppStartInfoPersistTask = this::persistProcessStartInfo; + IoThread.getHandler() + .postDelayed( + mAppStartInfoPersistTask, + immediately ? 0 : APP_START_INFO_PERSIST_INTERVAL); + } + } + } + + /** Helper function for testing only. */ + @VisibleForTesting + void clearProcessStartInfo(boolean removeFile) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (mAppStartInfoPersistTask != null) { + IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); + mAppStartInfoPersistTask = null; + } + if (removeFile && mProcStartInfoFile != null) { + mProcStartInfoFile.delete(); + } + mData.getMap().clear(); + } + } + + /** + * Helper functions for shell command. + * > adb shell dumpsys activity clear-start-info [package-name] + */ + void clearHistoryProcessStartInfo(String packageName, int userId) { + if (!mEnabled) { + return; + } + Optional<Integer> appId = Optional.empty(); + if (TextUtils.isEmpty(packageName)) { + synchronized (mLock) { + removeByUserIdLocked(userId); + } + } else { + final int uid = + mService.mPackageManagerInt.getPackageUid( + packageName, PackageManager.MATCH_ALL, userId); + appId = Optional.of(UserHandle.getAppId(uid)); + synchronized (mLock) { + removePackageLocked(packageName, uid, true, userId); + } + } + schedulePersistProcessStartInfo(true); + } + + /** + * Helper functions for shell command. + * > adb shell dumpsys activity start-info [package-name] + */ + void dumpHistoryProcessStartInfo(PrintWriter pw, String packageName) { + if (!mEnabled) { + return; + } + pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity start-info)"); + SimpleDateFormat sdf = new SimpleDateFormat(); + synchronized (mLock) { + pw.println("Last Timestamp of Persistence Into Persistent Storage: " + + sdf.format(new Date(mLastAppStartInfoPersistTimestamp))); + if (TextUtils.isEmpty(packageName)) { + forEachPackageLocked((name, records) -> { + dumpHistoryProcessStartInfoLocked(pw, " ", name, records, sdf); + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + } else { + SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName); + if (array != null) { + dumpHistoryProcessStartInfoLocked(pw, " ", packageName, array, sdf); + } + } + } + } + + @GuardedBy("mLock") + private void dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix, + String packageName, SparseArray<AppStartInfoContainer> array, + SimpleDateFormat sdf) { + pw.println(prefix + "package: " + packageName); + int size = array.size(); + for (int i = 0; i < size; i++) { + pw.println(prefix + " Historical Process Start for userId=" + array.keyAt(i)); + array.valueAt(i).dumpLocked(pw, prefix + " ", sdf); + } + } + + /** Convenience method to obtain timestamp of beginning of start.*/ + private static long getStartTimestamp(ApplicationStartInfo startInfo) { + return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH); + } + + /** A container class of (@link android.app.ApplicationStartInfo) */ + final class AppStartInfoContainer { + private List<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp. + private int mMaxCapacity; + private int mUid; + + AppStartInfoContainer(final int maxCapacity) { + mInfos = new ArrayList<ApplicationStartInfo>(); + mMaxCapacity = maxCapacity; + } + + @GuardedBy("mLock") + void getStartInfoLocked( + final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) { + results.addAll(mInfos.size() <= maxNum ? 0 : mInfos.size() - maxNum, mInfos); + } + + @GuardedBy("mLock") + void addStartInfoLocked(ApplicationStartInfo info) { + int size = mInfos.size(); + if (size >= mMaxCapacity) { + // Remove oldest record if size is over max capacity. + int oldestIndex = -1; + long oldestTimeStamp = Long.MAX_VALUE; + for (int i = 0; i < size; i++) { + ApplicationStartInfo startInfo = mInfos.get(i); + if (getStartTimestamp(startInfo) < oldestTimeStamp) { + oldestTimeStamp = getStartTimestamp(startInfo); + oldestIndex = i; + } + } + if (oldestIndex >= 0) { + mInfos.remove(oldestIndex); + } + mInfos.remove(0); + } + mInfos.add(info); + Collections.sort(mInfos, (a, b) -> + Long.compare(getStartTimestamp(b), getStartTimestamp(a))); + } + + @GuardedBy("mLock") + void addTimestampToStartLocked(int key, long timestampNs) { + int index = mInfos.size() - 1; + int startupState = mInfos.get(index).getStartupState(); + if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED + || key == ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { + mInfos.get(index).addStartupTimestamp(key, timestampNs); + } + } + + @GuardedBy("mLock") + void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { + int size = mInfos.size(); + for (int i = 0; i < size; i++) { + mInfos.get(i).dump(pw, prefix + " ", "#" + i, sdf); + } + } + + @GuardedBy("mLock") + void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { + long token = proto.start(fieldId); + proto.write(AppsStartInfoProto.Package.User.UID, mUid); + int size = mInfos.size(); + for (int i = 0; i < size; i++) { + mInfos.get(i) + .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + } + proto.end(token); + } + + int readFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + long token = proto.start(fieldId); + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.Package.User.UID: + mUid = proto.readInt(AppsStartInfoProto.Package.User.UID); + break; + case (int) AppsStartInfoProto.Package.User.APP_START_INFO: + ApplicationStartInfo info = new ApplicationStartInfo(); + info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + mInfos.add(info); + break; + } + } + proto.end(token); + return mUid; + } + } +} diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index f04198ed39ec..614caffe2f57 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -494,6 +494,10 @@ public final class ProcessList { @GuardedBy("mService") final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>(); + /** Manages the {@link android.app.ApplicationStartInfo} records. */ + @GuardedBy("mAppStartInfoTracker") + final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker(); + /** * The currently running SDK sandbox processes for a uid. */ @@ -956,12 +960,14 @@ public final class ProcessList { mSystemServerSocketForZygote.getFileDescriptor(), EVENT_INPUT, this::handleZygoteMessages); } + mAppStartInfoTracker.init(mService); mAppExitInfoTracker.init(mService); mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper()); } } void onSystemReady() { + mAppStartInfoTracker.onSystemReady(); mAppExitInfoTracker.onSystemReady(); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 0dd579fd0b15..728bacea3380 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -658,8 +658,8 @@ class UserController implements Handler.Callback { mInjector.getUserJourneyLogger() .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_BEGIN); - // If the user key hasn't been unlocked yet, we cannot proceed. - if (!StorageManager.isUserKeyUnlocked(userId)) return false; + // If the user's CE storage hasn't been unlocked yet, we cannot proceed. + if (!StorageManager.isCeStorageUnlocked(userId)) return false; synchronized (mLock) { // Do not proceed if unexpected state or a stale user if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) { @@ -674,8 +674,8 @@ class UserController implements Handler.Callback { // Call onBeforeUnlockUser on a worker thread that allows disk I/O FgThread.getHandler().post(() -> { - if (!StorageManager.isUserKeyUnlocked(userId)) { - Slogf.w(TAG, "User key got locked unexpectedly, leaving user locked."); + if (!StorageManager.isCeStorageUnlocked(userId)) { + Slogf.w(TAG, "User's CE storage got locked unexpectedly, leaving user locked."); return; } @@ -709,8 +709,8 @@ class UserController implements Handler.Callback { private void finishUserUnlocked(final UserState uss) { final int userId = uss.mHandle.getIdentifier(); EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKED, userId); - // Only keep marching forward if user is actually unlocked - if (!StorageManager.isUserKeyUnlocked(userId)) return; + // Only keep marching forward if the user's CE storage is unlocked. + if (!StorageManager.isCeStorageUnlocked(userId)) return; synchronized (mLock) { // Bail if we ended up with a stale user if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return; @@ -796,8 +796,8 @@ class UserController implements Handler.Callback { if (userInfo == null) { return; } - // Only keep marching forward if user is actually unlocked - if (!StorageManager.isUserKeyUnlocked(userId)) return; + // Only keep marching forward if the user's CE storage is unlocked. + if (!StorageManager.isCeStorageUnlocked(userId)) return; // Remember that we logged in mInjector.getUserManager().onUserLoggedIn(userId); @@ -1330,7 +1330,7 @@ class UserController implements Handler.Callback { } try { Slogf.i(TAG, "Locking CE storage for user #" + userId); - mInjector.getStorageManager().lockUserKey(userId); + mInjector.getStorageManager().lockCeStorage(userId); } catch (RemoteException re) { throw re.rethrowAsRuntimeException(); } @@ -1946,8 +1946,8 @@ class UserController implements Handler.Callback { } UserState uss; - if (!StorageManager.isUserKeyUnlocked(userId)) { - // We always want to try to unlock the user key, even if the user is not started yet. + if (!StorageManager.isCeStorageUnlocked(userId)) { + // We always want to try to unlock CE storage, even if the user is not started yet. mLockPatternUtils.unlockUserKeyIfUnsecured(userId); } synchronized (mLock) { @@ -2750,10 +2750,10 @@ class UserController implements Handler.Callback { case UserState.STATE_RUNNING_UNLOCKING: case UserState.STATE_RUNNING_UNLOCKED: return true; - // In the stopping/shutdown state return unlock state of the user key + // In the stopping/shutdown state, return unlock state of the user's CE storage. case UserState.STATE_STOPPING: case UserState.STATE_SHUTDOWN: - return StorageManager.isUserKeyUnlocked(userId); + return StorageManager.isCeStorageUnlocked(userId); default: return false; } @@ -2762,10 +2762,10 @@ class UserController implements Handler.Callback { switch (state.state) { case UserState.STATE_RUNNING_UNLOCKED: return true; - // In the stopping/shutdown state return unlock state of the user key + // In the stopping/shutdown state, return unlock state of the user's CE storage. case UserState.STATE_STOPPING: case UserState.STATE_SHUTDOWN: - return StorageManager.isUserKeyUnlocked(userId); + return StorageManager.isCeStorageUnlocked(userId); default: return false; } diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 292fc14ac6eb..51cb9505ed4f 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -165,7 +165,8 @@ import java.util.Objects; @Override public String toString() { - return "type: " + mDeviceType + "internal type: " + mInternalDeviceType + return "type: " + mDeviceType + + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) + " addr: " + mDeviceAddress + " bt audio type: " + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index eea3d3885b34..2336753c88f9 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1252,8 +1252,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { - mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -1262,8 +1262,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1281,8 +1281,8 @@ public class AudioDeviceBroker { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { - mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -1290,6 +1290,11 @@ public class AudioDeviceBroker { mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); } + /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked( + List<AudioDeviceAttributes> devices) { + return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices); + } + /*package*/ void registerCommunicationDeviceDispatcher( @NonNull ICommunicationDeviceDispatcher dispatcher) { mCommDevDispatchers.register(dispatcher); @@ -2684,4 +2689,5 @@ public class AudioDeviceBroker { void clearDeviceInventory() { mDeviceInventory.clearDeviceInventory(); } + } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e08fdd65ad94..a1d2e1412777 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -121,6 +121,26 @@ public class AudioDeviceInventory { } /** + * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink + * Bluetooth device and no corresponding entry already exists. + * @param ada the device to add if needed + */ + void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) { + if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) { + return; + } + synchronized (mDeviceInventoryLock) { + if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) { + return; + } + AdiDeviceState ads = new AdiDeviceState( + ada.getType(), ada.getInternalType(), ada.getAddress()); + mDeviceInventory.put(ads.getDeviceId(), ads); + } + mDeviceBroker.persistAudioDeviceSettings(); + } + + /** * Adds a new AdiDeviceState or updates the audio device cateogory of the matching * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update @@ -992,8 +1012,8 @@ public class AudioDeviceInventory { /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { - mPrefDevDispatchers.register(dispatcher); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { + mPrefDevDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -1002,8 +1022,8 @@ public class AudioDeviceInventory { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mNonDefDevDispatchers.register(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mNonDefDevDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1084,8 +1104,8 @@ public class AudioDeviceInventory { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { - mDevRoleCapturePresetDispatchers.register(dispatcher); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { + mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -1414,6 +1434,8 @@ public class AudioDeviceInventory { updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/); if (!connect) { purgeDevicesRoles_l(); + } else { + addAudioDeviceInInventoryIfNeeded(attributes); } } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -1702,6 +1724,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); + addAudioDeviceInInventoryIfNeeded(ada); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -2006,9 +2029,9 @@ public class AudioDeviceInventory { final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); - - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address, name), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address, name); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( @@ -2018,6 +2041,7 @@ public class AudioDeviceInventory { mDeviceBroker.postApplyVolumeOnDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); + addAudioDeviceInInventoryIfNeeded(ada); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, @@ -2128,6 +2152,7 @@ public class AudioDeviceInventory { sensorUuid)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); + addAudioDeviceInInventoryIfNeeded(ada); } if (streamType == AudioSystem.STREAM_DEFAULT) { @@ -2462,6 +2487,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mPrefDevDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; i++) { try { + if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged( strategy, devices); } catch (RemoteException e) { @@ -2475,6 +2503,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; i++) { try { + if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged( strategy, devices); } catch (RemoteException e) { @@ -2488,6 +2519,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; ++i) { try { + if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged( capturePreset, role, devices); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3243385c3b18..b29eda17b882 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -16,8 +16,8 @@ package com.android.server.audio; -import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; @@ -36,6 +36,7 @@ import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; + import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -149,6 +150,7 @@ import android.media.permission.SafeCloseable; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionManager; +import android.media.session.MediaSessionManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -201,6 +203,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.media.audio.flags.Flags; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -307,6 +310,9 @@ public class AudioService extends IAudioService.Stub private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; + /** do not use directly, use getMediaSessionManager() which handles lazy initialization */ + @Nullable private volatile MediaSessionManager mMediaSessionManager; + // the platform type affects volume and silent mode behavior private final int mPlatformType; @@ -938,6 +944,8 @@ public class AudioService extends IAudioService.Stub private final SoundDoseHelper mSoundDoseHelper; + private final HardeningEnforcer mHardeningEnforcer; + private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -1312,6 +1320,8 @@ public class AudioService extends IAudioService.Stub mDisplayManager = context.getSystemService(DisplayManager.class); mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); + + mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); } private void initVolumeStreamStates() { @@ -1381,7 +1391,6 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); - } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1394,6 +1403,14 @@ public class AudioService extends IAudioService.Stub } }; + private MediaSessionManager getMediaSessionManager() { + if (mMediaSessionManager == null) { + mMediaSessionManager = (MediaSessionManager) mContext + .getSystemService(Context.MEDIA_SESSION_SERVICE); + } + return mMediaSessionManager; + } + /** * Initialize intent receives and settings observers for this service. * Must be called after createStreamStates() as the handling of some events @@ -2881,7 +2898,7 @@ public class AudioService extends IAudioService.Stub // IPC methods /////////////////////////////////////////////////////////////////////////// /** - * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes) + * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes) * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, * List<AudioDeviceAttributes>) */ @@ -2891,8 +2908,11 @@ public class AudioService extends IAudioService.Stub if (devices == null) { return AudioSystem.ERROR; } + + devices = retrieveBluetoothAddresses(devices); + final String logString = String.format( - "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s", + "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, devices.stream().map(e -> e.toString()).collect(Collectors.joining(","))); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2948,7 +2968,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -2963,6 +2983,9 @@ public class AudioService extends IAudioService.Stub @NonNull AudioDeviceAttributes device) { super.setDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString()); @@ -2989,6 +3012,9 @@ public class AudioService extends IAudioService.Stub AudioDeviceAttributes device) { super.removeDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString()); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -3023,7 +3049,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -3036,7 +3062,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyPreferredDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener( @@ -3060,7 +3087,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener( @@ -3076,7 +3104,7 @@ public class AudioService extends IAudioService.Stub } /** - * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes) */ public int setPreferredDevicesForCapturePreset( int capturePreset, List<AudioDeviceAttributes> devices) { @@ -3095,6 +3123,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } + devices = retrieveBluetoothAddresses(devices); + final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( capturePreset, devices); if (status != AudioSystem.SUCCESS) { @@ -3141,7 +3171,7 @@ public class AudioService extends IAudioService.Stub status, capturePreset)); return new ArrayList<AudioDeviceAttributes>(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -3155,7 +3185,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher( + dispatcher, isBluetoothPrividged()); } /** @@ -3175,7 +3206,9 @@ public class AudioService extends IAudioService.Stub public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { enforceQueryStateOrModifyRoutingPermission(); - return getDevicesForAttributesInt(attributes, false /* forVolume */); + + return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) @@ -3185,7 +3218,8 @@ public class AudioService extends IAudioService.Stub */ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected( @NonNull AudioAttributes attributes) { - return getDevicesForAttributesInt(attributes, false /* forVolume */); + return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @@ -3405,6 +3439,10 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags, String callingPackage, String attributionTag) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) { + return; + } if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); @@ -4181,6 +4219,10 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, String callingPackage, String attributionTag) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) { + return; + } setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null, callingPackage, attributionTag); } @@ -5033,6 +5075,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { + super.setMasterMute_enforcePermission(); setMasterMuteInternal(mute, flags, callingPackage, @@ -5398,6 +5441,10 @@ public class AudioService extends IAudioService.Stub } public void setRingerModeExternal(int ringerMode, String caller) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) { + return; + } if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode) && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); @@ -6150,6 +6197,35 @@ public class AudioService extends IAudioService.Stub AudioDeviceVolumeManager.ADJUST_MODE_NORMAL); } + /** + * @see AudioManager#adjustVolume(int, int) + * This method is redirected from AudioManager to AudioService for API hardening rules + * enforcement then to MediaSession for implementation. + */ + @Override + public void adjustVolume(int direction, int flags) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) { + return; + } + getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, + direction, flags); + } + + /** + * @see AudioManager#adjustSuggestedStreamVolume(int, int, int) + * This method is redirected from AudioManager to AudioService for API hardening rules + * enforcement then to MediaSession for implementation. + */ + @Override + public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) { + return; + } + getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags); + } + /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */ @Override public void setStreamVolumeForUid(int streamType, int index, int flags, @@ -7390,6 +7466,8 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + device = retrieveBluetoothAddress(device); + sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" + device.getAddress() + " behavior:" @@ -7473,6 +7551,8 @@ public class AudioService extends IAudioService.Stub // verify parameters Objects.requireNonNull(device); + device = retrieveBluetoothAddress(device); + return getDeviceVolumeBehaviorInt(device); } @@ -7547,9 +7627,12 @@ public class AudioService extends IAudioService.Stub /** * see AudioManager.setWiredDeviceConnectionState() */ - public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes, @ConnectionState int state, String caller) { super.setWiredDeviceConnectionState_enforcePermission(); + Objects.requireNonNull(attributes); + + attributes = retrieveBluetoothAddress(attributes); if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { @@ -7590,6 +7673,9 @@ public class AudioService extends IAudioService.Stub boolean connected) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + device = retrieveBluetoothAddress(device); + mDeviceBroker.setTestDeviceConnectionState(device, connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED); // simulate a routing update from native @@ -10422,6 +10508,103 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } + private boolean isBluetoothPrividged() { + if (!Flags.bluetoothMacAddressAnonymization()) { + return true; + } + return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BLUETOOTH_CONNECT) + || Binder.getCallingUid() == Process.SYSTEM_UID; + } + + List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) { + if (isBluetoothPrividged()) { + return devices; + } + + List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>(); + for (AudioDeviceAttributes ada : devices) { + if (ada == null) { + continue; + } + checkedDevices.add(retrieveBluetoothAddressUncheked(ada)); + } + return checkedDevices; + } + + AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) { + if (isBluetoothPrividged()) { + return ada; + } + return retrieveBluetoothAddressUncheked(ada); + } + + AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) { + Objects.requireNonNull(ada); + if (AudioSystem.isBluetoothDevice(ada.getInternalType())) { + String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress()); + for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) { + if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType()) + && (ada.getInternalType() == ads.getInternalDeviceType()) + && anonymizedAddress.equals(anonymizeBluetoothAddress( + ads.getDeviceAddress())))) { + continue; + } + ada.setAddress(ads.getDeviceAddress()); + break; + } + } + return ada; + } + + /** + * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app + * Must match the implementation of BluetoothUtils.toAnonymizedAddress() + * @param address Mac address to be anonymized + * @return anonymized mac address + */ + static String anonymizeBluetoothAddress(String address) { + if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) { + return null; + } + return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length()); + } + + private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList( + List<AudioDeviceAttributes> devices) { + if (isBluetoothPrividged()) { + return devices; + } + return anonymizeAudioDeviceAttributesListUnchecked(devices); + } + + /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked( + List<AudioDeviceAttributes> devices) { + List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>(); + for (AudioDeviceAttributes ada : devices) { + anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada)); + } + return anonymizedDevices; + } + + private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked( + AudioDeviceAttributes ada) { + if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) { + return ada; + } + AudioDeviceAttributes res = new AudioDeviceAttributes(ada); + res.setAddress(anonymizeBluetoothAddress(ada.getAddress())); + return res; + } + + private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) { + if (isBluetoothPrividged()) { + return ada; + } + + return anonymizeAudioDeviceAttributesUnchecked(ada); + } + //========================================================================================== // camera sound is forced if any of the resources corresponding to one active SIM @@ -10469,13 +10652,16 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(usages); Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + if (timeOutMs <= 0 || usages.length == 0) { throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute"); } Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs + " usages:" + Arrays.toString(usages)); - if (mDeviceBroker.isDeviceConnected(device)) { + if (mDeviceBroker.isDeviceConnected(ada)) { // not throwing an exception as there could be a race between a connection (server-side, // notification of connection in flight) and a mute operation (client-side) Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected"); @@ -10487,12 +10673,19 @@ public class AudioService extends IAudioService.Stub + mMutingExpectedDevice); throw new IllegalStateException("muteAwaitConnection already in progress"); } - mMutingExpectedDevice = device; + mMutingExpectedDevice = ada; mMutedUsagesAwaitingConnection = usages; - mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs); + mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs); } - dispatchMuteAwaitConnection(cb -> { try { - cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } }); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnMutedUntilConnection(dev, usages); + } catch (RemoteException e) { } + }); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @@ -10501,7 +10694,7 @@ public class AudioService extends IAudioService.Stub super.getMutingExpectedDevice_enforcePermission(); synchronized (mMuteAwaitConnectionLock) { - return mMutingExpectedDevice; + return anonymizeAudioDeviceAttributes(mMutingExpectedDevice); } } @@ -10510,6 +10703,9 @@ public class AudioService extends IAudioService.Stub public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + Log.i(TAG, "cancelMuteAwaitConnection for device:" + device); final int[] mutedUsages; synchronized (mMuteAwaitConnectionLock) { @@ -10519,7 +10715,7 @@ public class AudioService extends IAudioService.Stub Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device"); return; } - if (!device.equalTypeAddress(mMutingExpectedDevice)) { + if (!ada.equalTypeAddress(mMutingExpectedDevice)) { Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device + "] but expected device is" + mMutingExpectedDevice); throw new IllegalStateException("cancelMuteAwaitConnection for wrong device"); @@ -10529,8 +10725,14 @@ public class AudioService extends IAudioService.Stub mMutedUsagesAwaitingConnection = null; mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnUnmutedEvent( + AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages); } catch (RemoteException e) { } }); } @@ -10544,7 +10746,7 @@ public class AudioService extends IAudioService.Stub super.registerMuteAwaitConnectionDispatcher_enforcePermission(); if (register) { - mMuteAwaitConnectionDispatchers.register(cb); + mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged()); } else { mMuteAwaitConnectionDispatchers.unregister(cb); } @@ -10568,8 +10770,14 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor.cancelMuteAwaitConnection( "checkMuteAwaitConnection device " + device + " connected, unmuting"); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes ada = device; + if (!isPrivileged) { + ada = anonymizeAudioDeviceAttributesUnchecked(device); + } + cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, + ada, mutedUsages); } catch (RemoteException e) { } }); } @@ -10589,7 +10797,8 @@ public class AudioService extends IAudioService.Stub mMutingExpectedDevice = null; mMutedUsagesAwaitingConnection = null; } - dispatchMuteAwaitConnection(cb -> { try { + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { cb.dispatchOnUnmutedEvent( AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT, timedOutDevice, mutedUsages); @@ -10597,13 +10806,14 @@ public class AudioService extends IAudioService.Stub } private void dispatchMuteAwaitConnection( - java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) { + java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) { final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast(); // lazy initialization as errors unlikely ArrayList<IMuteAwaitConnectionCallback> errorList = null; for (int i = 0; i < nbDispatchers; i++) { try { - callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i)); + callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i), + (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i)); } catch (Exception e) { if (errorList == null) { errorList = new ArrayList<>(1); @@ -13243,6 +13453,9 @@ public class AudioService extends IAudioService.Stub @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) { Objects.requireNonNull(device, "device must not be null"); enforceModifyAudioRoutingPermission(); + + device = retrieveBluetoothAddress(device); + final String getterKey = "additional_output_device_delay=" + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id. final String setterKey = getterKey + "," + delayMillis; // append the delay for setter @@ -13263,6 +13476,9 @@ public class AudioService extends IAudioService.Stub @IntRange(from = 0) public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device, "device must not be null"); + + device = retrieveBluetoothAddress(device); + final String key = "additional_output_device_delay"; final String reply = AudioSystem.getParameters( key + "=" + device.getInternalType() + "," + device.getAddress()); @@ -13290,6 +13506,9 @@ public class AudioService extends IAudioService.Stub @IntRange(from = 0) public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device, "device must not be null"); + + device = retrieveBluetoothAddress(device); + final String key = "max_additional_output_device_delay"; final String reply = AudioSystem.getParameters( key + "=" + device.getInternalType() + "," + device.getAddress()); diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 6af409e7af3a..7d7e6d000f62 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -44,7 +44,9 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -66,6 +68,8 @@ public class BtHelper { // Bluetooth headset device private @Nullable BluetoothDevice mBluetoothHeadsetDevice; + private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = + new HashMap<>(); private @Nullable BluetoothHearingAid mHearingAid; @@ -590,7 +594,16 @@ public class BtHelper { if (mBluetoothHeadsetDevice == null) { return null; } - return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); + return getHeadsetAudioDevice(mBluetoothHeadsetDevice); + } + + private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) { + AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice); + if (deviceAttr != null) { + // Returns the cached device attributes so that it is consistent as the previous one. + return deviceAttr; + } + return btHeadsetDeviceToAudioDevice(btDevice); } private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { @@ -628,7 +641,7 @@ public class BtHelper { return true; } int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); + AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); boolean result = false; if (isActive) { result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); @@ -648,6 +661,13 @@ public class BtHelper { result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( inDevice, audioDevice.getAddress(), audioDevice.getName()), isActive, btDevice) && result; + if (result) { + if (isActive) { + mResolvedScoAudioDevices.put(btDevice, audioDevice); + } else { + mResolvedScoAudioDevices.remove(btDevice); + } + } return result; } diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java new file mode 100644 index 000000000000..c7556dacb783 --- /dev/null +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -0,0 +1,109 @@ +/* + * 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 com.android.server.audio; + +import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.os.Binder; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +/** + * Class to encapsulate all audio API hardening operations + */ +public class HardeningEnforcer { + + private static final String TAG = "AS.HardeningEnforcer"; + + final Context mContext; + final boolean mIsAutomotive; + + /** + * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100; + /** + * Matches calls from {@link AudioManager#adjustVolume(int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101; + /** + * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102; + /** + * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103; + /** + * Matches calls from {@link AudioManager#setRingerMode(int)} + */ + public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; + + public HardeningEnforcer(Context ctxt, boolean isAutomotive) { + mContext = ctxt; + mIsAutomotive = isAutomotive; + } + + /** + * Checks whether the call in the current thread should be allowed or blocked + * @param volumeMethod name of the method to check, for logging purposes + * @return false if the method call is allowed, true if it should be a no-op + */ + protected boolean blockVolumeMethod(int volumeMethod) { + // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED + if (mIsAutomotive) { + if (!autoPublicVolumeApiHardening()) { + // automotive hardening flag disabled, no blocking on auto + return false; + } + if (mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (Binder.getCallingUid() < UserHandle.AID_APP_START) { + return false; + } + // TODO metrics? + // TODO log for audio dumpsys? + Log.e(TAG, "Preventing volume method " + volumeMethod + " for " + + getPackNameForUid(Binder.getCallingUid())); + return true; + } + // not blocking + return false; + } + + private String getPackNameForUid(int uid) { + final long token = Binder.clearCallingIdentity(); + try { + final String[] names = mContext.getPackageManager().getPackagesForUid(uid); + if (names == null + || names.length == 0 + || TextUtils.isEmpty(names[0])) { + return "[" + uid + "]"; + } + return names[0]; + } finally { + Binder.restoreCallingIdentity(token); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 1760bb3c7a6a..4538cad513d6 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -41,7 +41,6 @@ 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; @@ -358,18 +357,6 @@ 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 9569f23e8d49..1898b8015462 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,7 +41,6 @@ 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; @@ -89,7 +88,6 @@ 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; @@ -107,8 +105,6 @@ 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; @@ -429,42 +425,6 @@ 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() { @@ -745,22 +705,6 @@ 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) { @@ -1100,7 +1044,6 @@ 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(); @@ -1215,7 +1158,6 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); mAuthSession = null; - notifyAuthSessionChanged(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -1244,7 +1186,6 @@ public class BiometricService extends SystemService { session.onDialogDismissed(reason, credentialAttestation); mAuthSession = null; - notifyAuthSessionChanged(); } private void handleOnTryAgainPressed(long requestId) { @@ -1294,7 +1235,6 @@ public class BiometricService extends SystemService { final boolean finished = session.onClientDied(); if (finished) { mAuthSession = null; - notifyAuthSessionChanged(); } } @@ -1409,16 +1349,6 @@ 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 @@ -1456,7 +1386,6 @@ public class BiometricService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } - notifyAuthSessionChanged(); } private void handleCancelAuthentication(long requestId) { @@ -1471,7 +1400,6 @@ 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/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS index b80f3402c19d..6ca7803a455c 100644 --- a/services/core/java/com/android/server/compat/overrides/OWNERS +++ b/services/core/java/com/android/server/compat/overrides/OWNERS @@ -1,2 +1,2 @@ -tomnatan@google.com +mcarli@google.com mariiasand@google.com diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 64b17e57ee16..84be521e81c9 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -588,8 +588,7 @@ public class SyncOperation { return wakeLockName; } return (wakeLockName = target.provider - + "/" + target.account.type - + "/" + target.account.name); + + "/" + target.account.type); } // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog. diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java index 3581981c6cec..58a654a1623c 100644 --- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java +++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java @@ -28,6 +28,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.display.utils.DebugUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -50,7 +51,10 @@ import java.util.Map; public class AmbientBrightnessStatsTracker { private static final String TAG = "AmbientBrightnessStatsTracker"; - private static final boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.AmbientBrightnessStatsTracker DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); @VisibleForTesting static final float[] BUCKET_BOUNDARIES_FOR_NEW_STATS = diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index 59844e1afd1c..bba5ba35dbc7 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.utils.DebugUtils; import com.android.server.display.utils.DeviceConfigParsingUtils; import java.io.PrintWriter; @@ -58,8 +59,10 @@ import java.util.function.Function; @Deprecated class BrightnessThrottler { private static final String TAG = "BrightnessThrottler"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.BrightnessThrottler DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static final int THROTTLING_INVALID = -1; private final Injector mInjector; diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index d55065064871..ac5dd203fff6 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -64,6 +64,7 @@ import com.android.internal.util.RingBuffer; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; +import com.android.server.display.utils.DebugUtils; import libcore.io.IoUtils; @@ -91,8 +92,10 @@ import java.util.concurrent.TimeUnit; public class BrightnessTracker { static final String TAG = "BrightnessTracker"; - static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.BrightnessTracker DEBUG && adb reboot' + static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static final String EVENTS_FILE = "brightness_events.xml"; private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml"; private static final int MAX_EVENTS = 100; diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 0d6635d5b6e4..3de188f08fb1 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -42,6 +42,7 @@ import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import com.android.server.display.utils.DebugUtils; import com.android.server.policy.WindowManagerPolicy; import libcore.io.Streams; @@ -66,7 +67,9 @@ import java.nio.FloatBuffer; final class ColorFade { private static final String TAG = "ColorFade"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.ColorFade DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); // The layer for the electron beam surface. // This is currently hardcoded to be one layer above the boot animation. diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index cfbe0c69b320..a0beedb1aa64 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -77,6 +77,7 @@ import com.android.server.display.config.ThermalThrottling; import com.android.server.display.config.ThresholdPoint; import com.android.server.display.config.UsiVersion; import com.android.server.display.config.XmlParser; +import com.android.server.display.utils.DebugUtils; import org.xmlpull.v1.XmlPullParserException; @@ -519,7 +520,10 @@ import javax.xml.datatype.DatatypeConfigurationException; */ public class DisplayDeviceConfig { private static final String TAG = "DisplayDeviceConfig"; - private static final boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayDeviceConfig DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN; diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java index ea52a3d4e45c..67e612d1fd99 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -24,6 +24,7 @@ import android.view.DisplayAddress; import com.android.internal.annotations.GuardedBy; import com.android.server.display.DisplayManagerService.SyncRoot; +import com.android.server.display.utils.DebugUtils; import java.util.ArrayList; import java.util.List; @@ -39,7 +40,10 @@ import java.util.function.Consumer; */ class DisplayDeviceRepository implements DisplayAdapter.Listener { private static final String TAG = "DisplayDeviceRepository"; - private static final Boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayDeviceRepository DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 57b2c24a8b88..b1c92a6c2643 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -161,6 +161,7 @@ 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.DebugUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.input.InputManagerInternal; import com.android.server.utils.FoldSettingProvider; @@ -225,7 +226,10 @@ import java.util.function.Consumer; */ public final class DisplayManagerService extends SystemService { private static final String TAG = "DisplayManagerService"; - private static final boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayManagerService DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); // When this system property is set to 0, WFD is forcibly disabled on boot. // When this system property is set to 1, WFD is forcibly enabled on boot. diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ce98559abe30..915f5db2898b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -79,6 +79,7 @@ import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceI import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; +import com.android.server.display.utils.DebugUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; @@ -115,7 +116,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; - private static final boolean DEBUG = false; + private static final String TAG = "DisplayPowerController"; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayPowerController DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; // If true, uses the color fade on animation. @@ -608,7 +613,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mClock = mInjector.getClock(); mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); - mTag = "DisplayPowerController[" + mDisplayId + "]"; + mTag = TAG + "[" + mDisplayId + "]"; mHighBrightnessModeMetadata = hbmMetadata; mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId); mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 0f0002781f93..fc596dce8189 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -82,6 +82,7 @@ import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsLi import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.state.DisplayStateController; +import com.android.server.display.utils.DebugUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; @@ -118,8 +119,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; - private static final boolean DEBUG = false; - + private static final String TAG = "DisplayPowerController2"; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); // If true, uses the color fade on animation. // We might want to turn this off if we cannot get a guarantee that the screen @@ -503,7 +506,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), () -> updatePowerState(), mDisplayId, mSensorManager); mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController); - mTag = "DisplayPowerController2[" + mDisplayId + "]"; + mTag = TAG + "[" + mDisplayId + "]"; mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 2c257a17af91..be03a808e166 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -26,6 +26,8 @@ import android.util.Slog; import android.view.Choreographer; import android.view.Display; +import com.android.server.display.utils.DebugUtils; + import java.io.PrintWriter; /** @@ -48,7 +50,9 @@ import java.io.PrintWriter; final class DisplayPowerState { private static final String TAG = "DisplayPowerState"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayPowerState DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static String COUNTER_COLOR_FADE = "ColorFadeLevel"; private final Handler mHandler; diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 39172b8252ed..a9f78fd5bb2a 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -38,6 +38,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.FrameworkStatsLog; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; import com.android.server.display.DisplayManagerService.Clock; +import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -54,7 +55,9 @@ import java.util.Iterator; class HighBrightnessModeController { private static final String TAG = "HighBrightnessModeController"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.HighBrightnessModeController DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); @VisibleForTesting static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index 3c522e74246a..0521b8ac4f3b 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -18,6 +18,8 @@ package com.android.server.display; import android.util.Slog; +import com.android.server.display.utils.DebugUtils; + import java.io.PrintWriter; import java.util.Arrays; @@ -27,7 +29,9 @@ import java.util.Arrays; public class HysteresisLevels { private static final String TAG = "HysteresisLevels"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private final float[] mBrighteningThresholdsPercentages; private final float[] mDarkeningThresholdsPercentages; diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index c55bc62f5ae6..f3425d2e2136 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -43,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; +import com.android.server.display.utils.DebugUtils; import com.android.server.utils.FoldSettingProvider; import java.io.PrintWriter; @@ -63,7 +64,9 @@ import java.util.function.Consumer; class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private static final String TAG = "LogicalDisplayMapper"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.LogicalDisplayMapper DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1; public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2; diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java index cd3a453d5e64..3fd58e8641c3 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java @@ -35,6 +35,7 @@ import android.view.WindowManager; import android.widget.TextView; import com.android.internal.util.DumpUtils; +import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; @@ -47,8 +48,10 @@ import java.io.PrintWriter; */ final class OverlayDisplayWindow implements DumpUtils.Dump { private static final String TAG = "OverlayDisplayWindow"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.OverlayDisplayWindow DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private final float INITIAL_SCALE = 0.5f; private final float MIN_SCALE = 0.3f; private final float MAX_SCALE = 1.0f; diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java index 1e13974f6ef4..7bc797125b0e 100644 --- a/services/core/java/com/android/server/display/WakelockController.java +++ b/services/core/java/com/android/server/display/WakelockController.java @@ -21,6 +21,7 @@ import android.hardware.display.DisplayManagerInternal; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -41,7 +42,11 @@ public final class WakelockController { @VisibleForTesting static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS; - private static final boolean DEBUG = false; + private static final String TAG = "WakelockController"; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.WakelockController DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); @IntDef(flag = true, prefix = "WAKE_LOCK_", value = { WAKE_LOCK_PROXIMITY_POSITIVE, @@ -100,7 +105,7 @@ public final class WakelockController { public WakelockController(int displayId, DisplayManagerInternal.DisplayPowerCallbacks callbacks) { mDisplayId = displayId; - mTag = "WakelockController[" + mDisplayId + "]"; + mTag = TAG + "[" + mDisplayId + "]"; mDisplayPowerCallbacks = callbacks; mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business"; mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed"; diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index e3d38e7a25e9..7660cf8a1c3a 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -41,6 +41,7 @@ import android.view.SurfaceControl; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -65,7 +66,9 @@ import java.util.Objects; final class WifiDisplayAdapter extends DisplayAdapter { private static final String TAG = "WifiDisplayAdapter"; - private static final boolean DEBUG = false; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.WifiDisplayAdapter DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1; diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index 955b8d95fd87..873598aa7ce2 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -45,6 +45,7 @@ import android.util.Slog; import android.view.Surface; import com.android.internal.util.DumpUtils; +import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; import java.net.Inet4Address; @@ -69,7 +70,10 @@ import java.util.Objects; */ final class WifiDisplayController implements DumpUtils.Dump { private static final String TAG = "WifiDisplayController"; - private static final boolean DEBUG = false; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.WifiDisplayController DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static final int DEFAULT_CONTROL_PORT = 7236; private static final int MAX_THROUGHPUT = 50; 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 d953e8e52365..7f3ea6a0a99e 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -21,6 +21,7 @@ import android.os.SystemProperties; import android.util.Slog; import com.android.server.display.feature.flags.Flags; +import com.android.server.display.utils.DebugUtils; import java.util.function.Supplier; @@ -28,9 +29,13 @@ import java.util.function.Supplier; * Utility class to read the flags used in the display manager server. */ public class DisplayManagerFlags { - private static final boolean DEBUG = false; private static final String TAG = "DisplayManagerFlags"; + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayManagerFlags DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + + private final FlagState mConnectedDisplayManagementFlagState = new FlagState( Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT, Flags::enableConnectedDisplayManagement); diff --git a/services/core/java/com/android/server/display/utils/DebugUtils.java b/services/core/java/com/android/server/display/utils/DebugUtils.java new file mode 100644 index 000000000000..14964956a30a --- /dev/null +++ b/services/core/java/com/android/server/display/utils/DebugUtils.java @@ -0,0 +1,36 @@ +/* + * 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.utils; + +import android.util.Log; + +public class DebugUtils { + + public static final boolean DEBUG_ALL = Log.isLoggable("DisplayManager_All", Log.DEBUG); + + /** + * Returns whether the specified tag has logging enabled. Use the tag name specified in the + * calling class, or DisplayManager_All to globally enable all tags in display. + * To enable: + * adb shell setprop persist.log.tag.DisplayManager_All DEBUG + * To disable: + * adb shell setprop persist.log.tag.DisplayManager_All \"\" + */ + public static boolean isDebuggable(String tag) { + return Log.isLoggable(tag, Log.DEBUG) || DEBUG_ALL; + } +} diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp new file mode 100644 index 000000000000..067288d6650d --- /dev/null +++ b/services/core/java/com/android/server/feature/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "dropbox_flags", + package: "com.android.server.feature.flags", + srcs: [ + "dropbox_flags.aconfig", + ], +} + +java_aconfig_library { + name: "dropbox_flags_lib", + aconfig_declarations: "dropbox_flags", +} diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig new file mode 100644 index 000000000000..fee4bf377ddc --- /dev/null +++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.feature.flags" + +flag{ + name: "enable_read_dropbox_permission" + namespace: "preload_safety" + description: "Feature flag for permission to Read dropbox data" + bug: "287512663" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index f35b045471a6..568618e0a065 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -697,9 +697,9 @@ public class LockSettingsService extends ILockSettings.Stub { return; } - if (isUserKeyUnlocked(userId)) { - // If storage is not locked, the user will be automatically unlocked so there is - // no need to show the notification. + if (isCeStorageUnlocked(userId)) { + // If the user's CE storage is already unlocked, then the user will be automatically + // unlocked, so there is no need to show the notification. return; } @@ -1030,8 +1030,8 @@ public class LockSettingsService extends ILockSettings.Stub { // they did have an SP then their CE key wasn't encrypted by it. // // If this gets interrupted (e.g. by the device powering off), there shouldn't be a - // problem since this will run again on the next boot, and setUserKeyProtection() is - // okay with the key being already protected by the given secret. + // problem since this will run again on the next boot, and setCeStorageProtection() is + // okay with the CE key being already protected by the given secret. if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) { for (UserInfo user : mUserManager.getAliveUsers()) { removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber); @@ -1066,7 +1066,7 @@ public class LockSettingsService extends ILockSettings.Stub { Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId); return; } - setUserKeyProtection(userId, result.syntheticPassword); + setCeStorageProtection(userId, result.syntheticPassword); } } @@ -2005,11 +2005,11 @@ public class LockSettingsService extends ILockSettings.Stub { mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext)); } - private void setUserKeyProtection(@UserIdInt int userId, SyntheticPassword sp) { + private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) { final byte[] secret = sp.deriveFileBasedEncryptionKey(); final long callingId = Binder.clearCallingIdentity(); try { - mStorageManager.setUserKeyProtection(userId, secret); + mStorageManager.setCeStorageProtection(userId, secret); } catch (RemoteException e) { throw new IllegalStateException("Failed to protect CE key for user " + userId, e); } finally { @@ -2017,11 +2017,11 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private boolean isUserKeyUnlocked(int userId) { + private boolean isCeStorageUnlocked(int userId) { try { - return mStorageManager.isUserKeyUnlocked(userId); + return mStorageManager.isCeStorageUnlocked(userId); } catch (RemoteException e) { - Slog.e(TAG, "failed to check user key locked state", e); + Slog.e(TAG, "Error checking whether CE storage is unlocked", e); return false; } } @@ -2032,8 +2032,8 @@ public class LockSettingsService extends ILockSettings.Stub { * This method doesn't throw exceptions because it is called opportunistically whenever a user * is started. Whether it worked or not can be detected by whether the key got unlocked or not. */ - private void unlockUserKey(@UserIdInt int userId, SyntheticPassword sp) { - if (isUserKeyUnlocked(userId)) { + private void unlockCeStorage(@UserIdInt int userId, SyntheticPassword sp) { + if (isCeStorageUnlocked(userId)) { Slogf.d(TAG, "CE storage for user %d is already unlocked", userId); return; } @@ -2041,7 +2041,7 @@ public class LockSettingsService extends ILockSettings.Stub { final String userType = isUserSecure(userId) ? "secured" : "unsecured"; final byte[] secret = sp.deriveFileBasedEncryptionKey(); try { - mStorageManager.unlockUserKey(userId, userInfo.serialNumber, secret); + mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, secret); Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId); } catch (RemoteException e) { Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId); @@ -2054,8 +2054,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void unlockUserKeyIfUnsecured(@UserIdInt int userId) { checkPasswordReadPermission(); synchronized (mSpManager) { - if (isUserKeyUnlocked(userId)) { + if (isCeStorageUnlocked(userId)) { Slogf.d(TAG, "CE storage for user %d is already unlocked", userId); + // This method actually does more than unlock CE storage. However, if CE storage is + // already unlocked, then the other parts must have already been done too. return; } if (isUserSecure(userId)) { @@ -2072,7 +2074,7 @@ public class LockSettingsService extends ILockSettings.Stub { return; } onSyntheticPasswordUnlocked(userId, result.syntheticPassword); - unlockUserKey(userId, result.syntheticPassword); + unlockCeStorage(userId, result.syntheticPassword); } } @@ -2775,7 +2777,7 @@ public class LockSettingsService extends ILockSettings.Stub { final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), LockscreenCredential.createNone(), sp, userId); setCurrentLskfBasedProtectorId(protectorId, userId); - setUserKeyProtection(userId, sp); + setCeStorageProtection(userId, sp); onSyntheticPasswordCreated(userId, sp); Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId); return sp; @@ -2836,7 +2838,7 @@ public class LockSettingsService extends ILockSettings.Stub { unlockKeystore(userId, sp); - unlockUserKey(userId, sp); + unlockCeStorage(userId, sp); unlockUser(userId); @@ -2900,7 +2902,7 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.clearSidForUser(userId); gateKeeperClearSecureUserId(userId); - unlockUserKey(userId, sp); + unlockCeStorage(userId, sp); unlockKeystore(userId, sp); setKeystorePassword(null, userId); removeBiometricsForUser(userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7ca56990f2d0..4b5d52f0a8de 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -244,6 +244,7 @@ import android.os.WorkSource; import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings; +import android.provider.Settings.Secure; import android.service.notification.Adjustment; import android.service.notification.Condition; import android.service.notification.ConversationChannelWrapper; @@ -2008,6 +2009,8 @@ public class NotificationManagerService extends SystemService { Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS); + private final Uri SHOW_NOTIFICATION_SNOOZE + = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE); SettingsObserver(Handler handler) { super(handler); @@ -2034,6 +2037,10 @@ public class NotificationManagerService extends SystemService { false, this, UserHandle.USER_ALL); resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS, false, this, UserHandle.USER_ALL); + + resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE, + false, this, UserHandle.USER_ALL); + update(null); } @@ -2083,6 +2090,14 @@ public class NotificationManagerService extends SystemService { if (uri == null || LOCK_SCREEN_SHOW_NOTIFICATIONS.equals(uri)) { mPreferencesHelper.updateLockScreenShowNotifications(); } + if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) { + final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver, + Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) + != 0; + if (!snoozeEnabled) { + unsnoozeAll(); + } + } } } @@ -7792,6 +7807,13 @@ public class NotificationManagerService extends SystemService { } } + private void unsnoozeAll() { + synchronized (mNotificationLock) { + mSnoozeHelper.repostAll(mUserProfiles.getCurrentProfileIds()); + handleSavePolicyFile(); + } + } + protected class CancelNotificationRunnable implements Runnable { private final int mCallingUid; private final int mCallingPid; diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS index 72c4529a73eb..9f16662fd749 100644 --- a/services/core/java/com/android/server/notification/OWNERS +++ b/services/core/java/com/android/server/notification/OWNERS @@ -1,6 +1,9 @@ -# Bug component: 1305560 +# Bug component: 78010 juliacr@google.com yurilin@google.com +aroederer@google.com +matiashe@google.com +valiiftime@google.com jeffdq@google.com dsandler@android.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 017698943fc9..8f5676b31594 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -296,6 +296,20 @@ public final class SnoozeHelper { } } + /** + * Unsnooze & repost all snoozed notifications for userId and its profiles + */ + protected void repostAll(IntArray userIds) { + synchronized (mLock) { + List<NotificationRecord> snoozedNotifications = getSnoozed(); + for (NotificationRecord r : snoozedNotifications) { + if (userIds.binarySearch(r.getUserId()) >= 0) { + repost(r.getKey(), r.getUserId(), false); + } + } + } + } + protected void repost(String key, boolean muteOnReturn) { synchronized (mLock) { final NotificationRecord r = mSnoozedNotifications.get(key); diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java index e53c4367a497..ca149c5d2f31 100644 --- a/services/core/java/com/android/server/os/SchedulingPolicyService.java +++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java @@ -219,6 +219,7 @@ public class SchedulingPolicyService extends ISchedulingPolicyService.Stub { case Process.AUDIOSERVER_UID: // fastcapture, fastmixer case Process.CAMERASERVER_UID: // camera high frame rate recording case Process.BLUETOOTH_UID: // Bluetooth audio playback + case Process.PHONE_UID: // phone call return true; default: return false; diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 8bd2982d1ead..a10bae92aea5 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -223,11 +223,6 @@ public final class DexOptHelper { pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; } - // TODO(b/251903639): Do this when ART Service is used, or remove it from here. - if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) { - mPm.mArtManagerService.compileLayouts(packageState, pkg); - } - int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0; String filter = getCompilerFilterForReason(pkgCompilationReason); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7cac870088d8..7c32cde6c1d8 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -59,7 +59,6 @@ import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY; import static com.android.server.pm.PackageManagerService.MIN_INSTALLABLE_TARGET_SDK; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.POST_INSTALL; -import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS; import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX; import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX; import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY; @@ -135,7 +134,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; -import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -167,7 +165,6 @@ import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; -import com.android.server.pm.dex.ViewCompiler; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; @@ -222,7 +219,6 @@ final class InstallPackageHelper { private final Context mContext; private final PackageDexOptimizer mPackageDexOptimizer; private final PackageAbiHelper mPackageAbiHelper; - private final ViewCompiler mViewCompiler; private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; private final UpdateOwnershipHelper mUpdateOwnershipHelper; @@ -246,7 +242,6 @@ final class InstallPackageHelper { mContext = pm.mInjector.getContext(); mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer(); mPackageAbiHelper = pm.mInjector.getAbiHelper(); - mViewCompiler = pm.mInjector.getViewCompiler(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); } @@ -2119,24 +2114,6 @@ final class InstallPackageHelper { // ignore; not possible for non-system app } } - // Successfully deleted the old package; proceed with replace. - // Update the in-memory copy of the previous code paths. - PackageSetting ps1 = mPm.mSettings.getPackageLPr( - installRequest.getExistingPackageName()); - if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP) - == 0) { - Set<String> oldCodePaths = ps1.getOldCodePaths(); - if (oldCodePaths == null) { - oldCodePaths = new ArraySet<>(); - } - if (oldPackage != null) { - Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath()); - Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths()); - } - ps1.setOldCodePaths(oldCodePaths); - } else { - ps1.setOldCodePaths(null); - } if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { PackageSetting ps2 = mPm.mSettings.getPackageLPr( @@ -2539,13 +2516,6 @@ final class InstallPackageHelper { && !isApex; if (performDexopt) { - // Compile the layout resources. - if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); - mViewCompiler.compileLayouts(ps, pkg.getBaseApkPath()); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); // This mirrors logic from commitReconciledScanResultLocked, where the library files diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 0ebd33b9cd81..4ed31636ad56 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -1128,14 +1128,6 @@ public class Installer extends SystemService { throw new InstallerException("Invalid instruction set: " + instructionSet); } - public boolean compileLayouts(String apkPath, String packageName, String outDexFile, int uid) { - try { - return mInstalld.compileLayouts(apkPath, packageName, outDexFile, uid); - } catch (RemoteException e) { - return false; - } - } - /** * Returns the visibility of the optimized artifacts. * diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 1135466ae1d4..c260be9a5124 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -56,6 +56,7 @@ import android.content.IntentSender; import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageInstallerCallback; @@ -94,6 +95,7 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -112,6 +114,8 @@ import com.android.internal.util.SizedInputStream; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.ArchiveState; +import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.DataInputStream; @@ -513,18 +517,27 @@ public class LauncherAppsService extends SystemService { @Override public ParceledListSlice<LauncherActivityInfoInternal> getLauncherActivities( - String callingPackage, String packageName, UserHandle user) throws RemoteException { + String callingPackage, @Nullable String packageName, UserHandle user) + throws RemoteException { ParceledListSlice<LauncherActivityInfoInternal> launcherActivities = - queryActivitiesForUser(callingPackage, + queryActivitiesForUser( + callingPackage, new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(packageName), user); - if (Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) { + if (Flags.archiving()) { + launcherActivities = + getActivitiesForArchivedApp(packageName, user, launcherActivities); + } + if (Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, + 1) + == 0) { return launcherActivities; } - if (launcherActivities == null) { + if (launcherActivities == null || launcherActivities.getList().isEmpty()) { // Cannot access profile, so we don't even return any hidden apps. return null; } @@ -565,15 +578,16 @@ public class LauncherAppsService extends SystemService { visiblePackages.add(info.getActivityInfo().packageName); } final List<ApplicationInfo> installedPackages = - mPackageManagerInternal.getInstalledApplications(/* flags= */ 0, - user.getIdentifier(), callingUid); + mPackageManagerInternal.getInstalledApplications( + /* flags= */ 0, user.getIdentifier(), callingUid); for (ApplicationInfo applicationInfo : installedPackages) { if (!visiblePackages.contains(applicationInfo.packageName)) { if (!shouldShowSyntheticActivity(user, applicationInfo)) { continue; } - LauncherActivityInfoInternal info = getHiddenAppActivityInfo( - applicationInfo.packageName, callingUid, user); + LauncherActivityInfoInternal info = + getHiddenAppActivityInfo( + applicationInfo.packageName, callingUid, user); if (info != null) { result.add(info); } @@ -585,6 +599,23 @@ public class LauncherAppsService extends SystemService { } } + private ParceledListSlice<LauncherActivityInfoInternal> getActivitiesForArchivedApp( + @Nullable String packageName, + UserHandle user, + ParceledListSlice<LauncherActivityInfoInternal> launcherActivities) { + final List<LauncherActivityInfoInternal> archivedActivities = + generateLauncherActivitiesForArchivedApp(packageName, user); + if (archivedActivities.isEmpty()) { + return launcherActivities; + } + if (launcherActivities == null) { + return new ParceledListSlice(archivedActivities); + } + List<LauncherActivityInfoInternal> result = launcherActivities.getList(); + result.addAll(archivedActivities); + return new ParceledListSlice(result); + } + private boolean shouldShowSyntheticActivity(UserHandle user, ApplicationInfo appInfo) { if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) { return false; @@ -650,23 +681,30 @@ public class LauncherAppsService extends SystemService { return null; } + if (component == null || component.getPackageName() == null) { + // should not happen + return null; + } + final int callingUid = injectBinderCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - final ActivityInfo activityInfo = mPackageManagerInternal.getActivityInfo(component, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - callingUid, user.getIdentifier()); + ActivityInfo activityInfo = + mPackageManagerInternal.getActivityInfo( + component, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + callingUid, + user.getIdentifier()); if (activityInfo == null) { - return null; - } - if (component == null || component.getPackageName() == null) { - // should not happen + if (Flags.archiving()) { + return getMatchingArchivedAppActivityInfo(component, user); + } return null; } final IncrementalStatesInfo incrementalStatesInfo = - mPackageManagerInternal.getIncrementalStatesInfo(component.getPackageName(), - callingUid, user.getIdentifier()); + mPackageManagerInternal.getIncrementalStatesInfo( + component.getPackageName(), callingUid, user.getIdentifier()); if (incrementalStatesInfo == null) { // package does not exist; should not happen return null; @@ -677,6 +715,26 @@ public class LauncherAppsService extends SystemService { } } + private @Nullable LauncherActivityInfoInternal getMatchingArchivedAppActivityInfo( + @NonNull ComponentName component, UserHandle user) { + List<LauncherActivityInfoInternal> archivedActivities = + generateLauncherActivitiesForArchivedApp(component.getPackageName(), user); + if (archivedActivities.isEmpty()) { + return null; + } + for (int i = 0; i < archivedActivities.size(); i++) { + if (archivedActivities.get(i).getComponentName().equals(component)) { + return archivedActivities.get(i); + } + } + Slog.w( + TAG, + TextUtils.formatSimple( + "Expected archived app component name: %s" + " is not available!", + component)); + return null; + } + @Override public ParceledListSlice getShortcutConfigActivities( String callingPackage, String packageName, UserHandle user) @@ -700,6 +758,96 @@ public class LauncherAppsService extends SystemService { } } + @NonNull + private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp( + @Nullable String packageName, UserHandle user) { + List<ApplicationInfo> applicationInfoList = + (packageName == null) + ? getApplicationInfoListForAllArchivedApps(user) + : getApplicationInfoForArchivedApp(packageName, user); + List<LauncherActivityInfoInternal> launcherActivityList = new ArrayList<>(); + for (int i = 0; i < applicationInfoList.size(); i++) { + ApplicationInfo applicationInfo = applicationInfoList.get(i); + PackageStateInternal packageState = + mPackageManagerInternal.getPackageStateInternal( + applicationInfo.packageName); + if (packageState == null) { + continue; + } + ArchiveState archiveState = + packageState.getUserStateOrDefault(user.getIdentifier()).getArchiveState(); + if (archiveState == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Expected package: %s to be archived but missing ArchiveState" + + " in PackageState.", + applicationInfo.packageName)); + continue; + } + List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList = + archiveState.getActivityInfos(); + for (int j = 0; j < archiveActivityInfoList.size(); j++) { + launcherActivityList.add( + constructLauncherActivityInfoForArchivedApp( + user, applicationInfo, archiveActivityInfoList.get(j))); + } + } + return launcherActivityList; + } + + private static LauncherActivityInfoInternal constructLauncherActivityInfoForArchivedApp( + UserHandle user, + ApplicationInfo applicationInfo, + ArchiveState.ArchiveActivityInfo archiveActivityInfo) { + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.isArchived = applicationInfo.isArchived; + activityInfo.applicationInfo = applicationInfo; + activityInfo.packageName = + archiveActivityInfo.getOriginalComponentName().getPackageName(); + activityInfo.name = archiveActivityInfo.getOriginalComponentName().getClassName(); + activityInfo.nonLocalizedLabel = archiveActivityInfo.getTitle(); + + return new LauncherActivityInfoInternal( + activityInfo, + new IncrementalStatesInfo( + false /* isLoading */, 1 /* progress */, 0 /* loadingCompletedTime */), + user); + } + + @NonNull + private List<ApplicationInfo> getApplicationInfoListForAllArchivedApps(UserHandle user) { + final int callingUid = injectBinderCallingUid(); + List<ApplicationInfo> installedApplicationInfoList = + mPackageManagerInternal.getInstalledApplications( + PackageManager.MATCH_ARCHIVED_PACKAGES, + user.getIdentifier(), + callingUid); + List<ApplicationInfo> archivedApplicationInfos = new ArrayList<>(); + for (int i = 0; i < installedApplicationInfoList.size(); i++) { + ApplicationInfo installedApplicationInfo = installedApplicationInfoList.get(i); + if (installedApplicationInfo != null && installedApplicationInfo.isArchived) { + archivedApplicationInfos.add(installedApplicationInfo); + } + } + return archivedApplicationInfos; + } + + @NonNull + private List<ApplicationInfo> getApplicationInfoForArchivedApp( + @NonNull String packageName, UserHandle user) { + final int callingUid = injectBinderCallingUid(); + ApplicationInfo applicationInfo = mPackageManagerInternal.getApplicationInfo( + packageName, + PackageManager.MATCH_ARCHIVED_PACKAGES, + callingUid, + user.getIdentifier()); + if (applicationInfo == null || !applicationInfo.isArchived) { + return Collections.EMPTY_LIST; + } + return List.of(applicationInfo); + } + private List<LauncherActivityInfoInternal> queryIntentLauncherActivities( Intent intent, int callingUid, UserHandle user) { final List<ResolveInfo> apps = mPackageManagerInternal.queryIntentActivities(intent, diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 781e5f891a43..42a97f7e05b4 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -17,6 +17,8 @@ package com.android.server.pm; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; +import static android.content.pm.ArchivedActivity.bytesFromBitmap; +import static android.content.pm.ArchivedActivity.drawableToBitmap; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; @@ -27,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.content.Context; @@ -43,9 +46,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.VersionedPackage; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -64,7 +64,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -216,10 +215,13 @@ public class PackageArchiver { ArchiveState createArchiveStateInternal(String packageName, int userId, List<LauncherActivityInfo> mainActivities, String installerPackage) throws IOException { + final int iconSize = mContext.getSystemService( + ActivityManager.class).getLauncherLargeIconSize(); + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); - Path iconPath = storeIcon(packageName, mainActivity, userId, i); + Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize); ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), @@ -249,7 +251,7 @@ public class PackageArchiver { @VisibleForTesting Path storeIcon(String packageName, LauncherActivityInfo mainActivity, - @UserIdInt int userId, int index) throws IOException { + @UserIdInt int userId, int index, int iconSize) throws IOException { int iconResourceId = mainActivity.getActivityInfo().getIconResource(); if (iconResourceId == 0) { // The app doesn't define an icon. No need to store anything. @@ -257,7 +259,7 @@ public class PackageArchiver { } File iconsDir = createIconsDir(userId); File iconFile = new File(iconsDir, packageName + "-" + index + ".png"); - Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0)); + Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize); try (FileOutputStream out = new FileOutputStream(iconFile)) { // Note: Quality is ignored for PNGs. if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) { @@ -547,29 +549,6 @@ public class PackageArchiver { return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR); } - private static Bitmap drawableToBitmap(Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - - } - - Bitmap bitmap; - if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { - // Needed for drawables that are just a single color. - bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - } else { - bitmap = - Bitmap.createBitmap( - drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); - } - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } - private static byte[] bytesFromBitmapFile(Path path) throws IOException { if (path == null) { return null; @@ -579,18 +558,6 @@ public class PackageArchiver { return bytesFromBitmap(BitmapFactory.decodeFile(path.toString())); } - private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException { - if (bitmap == null) { - return null; - } - - try (ByteArrayOutputStream baos = new ByteArrayOutputStream( - bitmap.getByteCount())) { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); - return baos.toByteArray(); - } - } - /** * Creates serializable archived activities from existing ArchiveState. */ @@ -627,8 +594,8 @@ public class PackageArchiver { /** * Creates serializable archived activities from launcher activities. */ - static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos) - throws IOException { + static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos, + int iconSize) throws IOException { if (infos == null || infos.isEmpty()) { throw new IllegalArgumentException("No launcher activities"); } @@ -642,9 +609,8 @@ 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))); + archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null : + bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize)); // TODO(b/298452477) Handle monochrome icons. archivedActivity.monochromeIconBitmap = null; activities.add(archivedActivity); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1bb20b4769f5..b9b590833f4a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -17,6 +17,7 @@ package com.android.server.pm; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; +import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; import static android.os.Process.INVALID_UID; import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; @@ -42,6 +43,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ApplicationInfo; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -621,6 +623,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public int createSession(SessionParams params, String installerPackageName, String callingAttributionTag, int userId) { try { + if (params.dataLoaderParams != null + && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the " + + "com.android.permission.USE_INSTALLER_V2 permission " + + "to use a data loader"); + } + return createSessionInternal(params, installerPackageName, callingAttributionTag, userId); } catch (IOException e) { @@ -639,14 +649,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new SecurityException("User restriction prevents installing"); } - if (params.dataLoaderParams != null - && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("You need the " - + "com.android.permission.USE_INSTALLER_V2 permission " - + "to use a data loader"); - } - // INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK // capability; ensure if this is set as the install reason the app has one of the necessary // signature permissions to perform the rollback. @@ -1043,7 +1045,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements return false; } - private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { + private PackageInstallerSession openSessionInternal(int sessionId) throws IOException { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (!checkOpenSessionAccess(session)) { @@ -1523,6 +1525,61 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle); } + @Override + public void installPackageArchived( + @NonNull ArchivedPackageParcel archivedPackageParcel, + @NonNull SessionParams params, + @NonNull IntentSender statusReceiver, + @NonNull String installerPackageName, + @NonNull UserHandle userHandle) { + Objects.requireNonNull(params); + Objects.requireNonNull(archivedPackageParcel); + Objects.requireNonNull(statusReceiver); + Objects.requireNonNull(installerPackageName); + Objects.requireNonNull(userHandle); + + final int callingUid = Binder.getCallingUid(); + final int userId = userHandle.getIdentifier(); + final Computer snapshot = mPm.snapshotComputer(); + snapshot.enforceCrossUserPermission(callingUid, userId, true, true, + "installPackageArchived"); + + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the " + + "com.android.permission.INSTALL_PACKAGES permission " + + "to request archived package install"); + } + + params.installFlags |= PackageManager.INSTALL_ARCHIVED; + if (params.dataLoaderParams != null) { + throw new IllegalArgumentException( + "Incompatible session param: dataLoaderParams has to be null"); + } + + params.setDataLoaderParams( + PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(null)); + var metadata = PackageManagerShellCommandDataLoader.Metadata.forArchived( + archivedPackageParcel); + + // Create and commit install archived session. + PackageInstallerSession session = null; + try { + var sessionId = createSessionInternal(params, installerPackageName, + null /*installerAttributionTag*/, userId); + session = openSessionInternal(sessionId); + session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(), + null /*signature*/); + session.commit(statusReceiver, false /*forTransfer*/); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } finally { + if (session != null) { + session.close(); + } + } + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 61b6b24ba97e..6b4ac5b4fa4e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -215,7 +215,6 @@ import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.ArtUtils; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; -import com.android.server.pm.dex.ViewCompiler; import com.android.server.pm.local.PackageManagerLocalImpl; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; @@ -812,8 +811,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService private final DexManager mDexManager; private final DynamicCodeLogger mDynamicCodeLogger; - final ViewCompiler mViewCompiler; - private final AtomicInteger mNextMoveId = new AtomicInteger(); final MovePackageHelper.MoveCallbacks mMoveCallbacks; @@ -1486,11 +1483,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService archPkg.archivedActivities = PackageArchiver.createArchivedActivities( archiveState); } else { + final int iconSize = mContext.getSystemService( + ActivityManager.class).getLauncherLargeIconSize(); + var mainActivities = mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName, userId); archPkg.archivedActivities = PackageArchiver.createArchivedActivities( - mainActivities); + mainActivities, iconSize); } } catch (Exception e) { throw new IllegalArgumentException("Package does not have a main activity", e); @@ -1670,7 +1670,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> new ArtManagerService(i.getContext(), i.getInstaller(), i.getInstallLock()), (i, pm) -> ApexManager.getInstance(), - (i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()), (i, pm) -> (IncrementalManager) i.getContext().getSystemService(Context.INCREMENTAL_SERVICE), (i, pm) -> new DefaultAppProvider(() -> context.getSystemService(RoleManager.class), @@ -1881,7 +1880,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService mProcessLoggingHandler = testParams.processLoggingHandler; mProtectedPackages = testParams.protectedPackages; mSeparateProcesses = testParams.separateProcesses; - mViewCompiler = testParams.viewCompiler; mRequiredVerifierPackages = testParams.requiredVerifierPackages; mRequiredInstallerPackage = testParams.requiredInstallerPackage; mRequiredUninstallerPackage = testParams.requiredUninstallerPackage; @@ -2044,7 +2042,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService mBackgroundDexOptService = injector.getBackgroundDexOptService(); mArtManagerService = injector.getArtManagerService(); mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper()); - mViewCompiler = injector.getViewCompiler(); mSharedLibraries = mInjector.getSharedLibrariesImpl(); mBackgroundHandler = injector.getBackgroundHandler(); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 5b770aab19ca..ebf1c04bee12 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -32,7 +32,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; -import com.android.server.pm.dex.ViewCompiler; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -112,7 +111,6 @@ public class PackageManagerServiceInjector { private final Singleton<ArtManagerService> mArtManagerServiceProducer; private final Singleton<ApexManager> mApexManagerProducer; - private final Singleton<ViewCompiler> mViewCompilerProducer; private final Singleton<IncrementalManager> mIncrementalManagerProducer; private final Singleton<DefaultAppProvider> @@ -164,7 +162,6 @@ public class PackageManagerServiceInjector { Producer<DynamicCodeLogger> dynamicCodeLoggerProducer, Producer<ArtManagerService> artManagerServiceProducer, Producer<ApexManager> apexManagerProducer, - Producer<ViewCompiler> viewCompilerProducer, Producer<IncrementalManager> incrementalManagerProducer, Producer<DefaultAppProvider> defaultAppProviderProducer, Producer<DisplayMetrics> displayMetricsProducer, @@ -214,7 +211,6 @@ public class PackageManagerServiceInjector { mArtManagerServiceProducer = new Singleton<>( artManagerServiceProducer); mApexManagerProducer = new Singleton<>(apexManagerProducer); - mViewCompilerProducer = new Singleton<>(viewCompilerProducer); mIncrementalManagerProducer = new Singleton<>( incrementalManagerProducer); mDefaultAppProviderProducer = new Singleton<>( @@ -339,10 +335,6 @@ public class PackageManagerServiceInjector { return mApexManagerProducer.get(this, mPackageManager); } - public ViewCompiler getViewCompiler() { - return mViewCompilerProducer.get(this, mPackageManager); - } - public Handler getBackgroundHandler() { return mBackgroundHandler; } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index ca572091486e..9428ef6c8ba9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -34,7 +34,6 @@ import com.android.internal.content.om.OverlayConfig; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; -import com.android.server.pm.dex.ViewCompiler; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -93,7 +92,6 @@ public final class PackageManagerServiceTestParams { public @Nullable String systemTextClassifierPackage; public @Nullable String overlayConfigSignaturePackage; public @NonNull String requiredSdkSandboxPackage; - public ViewCompiler viewCompiler; public @Nullable String retailDemoPackage; public @Nullable String recentsPackage; public @Nullable String ambientContextDetectionPackage; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index 9e7f04351327..a09e71311125 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -304,10 +304,6 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { public boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles, @NonNull Collection<String> removedFiles) { ShellCommand shellCommand = lookupShellCommand(mParams.getArguments()); - if (shellCommand == null) { - Slog.e(TAG, "Missing shell command."); - return false; - } try { for (InstallationFile file : addedFiles) { Metadata metadata = Metadata.fromByteArray(file.getMetadata()); @@ -317,11 +313,19 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { } switch (metadata.getMode()) { case Metadata.STDIN: { + if (shellCommand == null) { + Slog.e(TAG, "Missing shell command for Metadata.STDIN."); + return false; + } final ParcelFileDescriptor inFd = getStdInPFD(shellCommand); mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd); break; } case Metadata.LOCAL_FILE: { + if (shellCommand == null) { + Slog.e(TAG, "Missing shell command for Metadata.LOCAL_FILE."); + return false; + } ParcelFileDescriptor incomingFd = null; try { final String filePath = new String(metadata.getData(), diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index ad26d1fa8587..3cf5481589fe 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -121,10 +121,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Nullable private Map<String, Set<String>> mimeGroups; - @Deprecated - @Nullable - private Set<String> mOldCodePaths; - @Nullable private String[] usesSdkLibraries; @@ -722,15 +718,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } } - if (mOldCodePaths != null) { - if (other.mOldCodePaths != null) { - mOldCodePaths.clear(); - mOldCodePaths.addAll(other.mOldCodePaths); - } else { - mOldCodePaths = null; - } - } - copyMimeGroups(other.mimeGroups); pkgState.updateFrom(other.pkgState); onChanged(); @@ -1428,12 +1415,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public PackageSetting setOldCodePaths(Set<String> oldCodePaths) { - mOldCodePaths = oldCodePaths; - onChanged(); - return this; - } - public PackageSetting setUsesSdkLibraries(String[] usesSdkLibraries) { this.usesSdkLibraries = usesSdkLibraries; onChanged(); @@ -1593,11 +1574,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal //@formatter:off - @DataClass.Generated.Member - public @Deprecated @Nullable Set<String> getOldCodePaths() { - return mOldCodePaths; - } - /** * The path under which native libraries have been unpacked. This path is * always derived at runtime, and is only stored here for cleanup when a @@ -1731,10 +1707,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1698097434269L, + time = 1698188444364L, 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 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 @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\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()\nprivate 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)\npublic @com.android.internal.annotations.VisibleForTesting 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 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 @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 @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\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()\nprivate 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)\npublic @com.android.internal.annotations.VisibleForTesting 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 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 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)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 154ee6eda138..bb55a39f8e4b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -5095,9 +5095,9 @@ public class UserManagerService extends IUserManager.Stub { } } - t.traceBegin("createUserKey"); + t.traceBegin("createUserStorageKeys"); final StorageManager storage = mContext.getSystemService(StorageManager.class); - storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral()); + storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral()); t.traceEnd(); // Only prepare DE storage here. CE storage will be prepared later, when the user is @@ -5899,17 +5899,18 @@ public class UserManagerService extends IUserManager.Stub { private void removeUserState(final @UserIdInt int userId) { Slog.i(LOG_TAG, "Removing user state of user " + userId); - // Cleanup lock settings. This must happen before destroyUserKey(), since the user's DE - // storage must still be accessible for the lock settings state to be properly cleaned up. + // Cleanup lock settings. This requires that the user's DE storage still be accessible, so + // this must happen before destroyUserStorageKeys(). mLockPatternUtils.removeUser(userId); // Evict and destroy the user's CE and DE encryption keys. At this point, the user's CE and // DE storage is made inaccessible, except to delete its contents. try { - mContext.getSystemService(StorageManager.class).destroyUserKey(userId); + mContext.getSystemService(StorageManager.class).destroyUserStorageKeys(userId); } catch (IllegalStateException e) { // This may be simply because the user was partially created. - Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e); + Slog.i(LOG_TAG, "Destroying storage keys for user " + userId + + " failed, continuing anyway", e); } // Cleanup package manager settings diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index f4f03f4c9c4e..ae47aa823245 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -540,44 +540,6 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { } /** - * Compile layout resources in a given package. - */ - public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) { - try { - if (ps.isPrivileged() || pkg.isUseEmbeddedDex() - || pkg.isDefaultToDeviceProtectedStorage()) { - // Privileged apps prefer to load trusted code so they don't use compiled views. - // If the app is not privileged but prefers code integrity, also avoid compiling - // views. - // Also disable the view compiler for protected storage apps since there are - // selinux permissions required for writing to user_de. - return false; - } - final String packageName = pkg.getPackageName(); - final String apkPath = pkg.getSplits().get(0).getPath(); - final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId()); - if (dataDir == null) { - // The app is not installed on the target user and doesn't have a data dir - return false; - } - final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex"; - Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath + - ") to " + outDexFile); - final long callingId = Binder.clearCallingIdentity(); - try { - return mInstaller.compileLayouts(apkPath, packageName, outDexFile, - pkg.getUid()); - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - catch (Throwable e) { - Log.e("PackageManager", "Failed to compile layouts", e); - return false; - } - } - - /** * Build the profiles names for all the package code paths (excluding resource only paths). * Return the map [code path -> profile name]. */ diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java deleted file mode 100644 index 0026922492a5..000000000000 --- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java +++ /dev/null @@ -1,65 +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.server.pm.dex; - -import android.os.Binder; -import android.os.UserHandle; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.pm.Installer; -import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.pkg.PackageStateInternal; - -import java.io.File; - -public class ViewCompiler { - private final Object mInstallLock; - @GuardedBy("mInstallLock") - private final Installer mInstaller; - - public ViewCompiler(Object installLock, Installer installer) { - mInstallLock = installLock; - mInstaller = installer; - } - - public boolean compileLayouts(PackageStateInternal ps, String apkPath) { - try { - final String packageName = ps.getPackageName(); - File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId()); - if (dataDir == null) { - // The app is not installed on the target user and doesn't have a data dir - return false; - } - final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex"; - Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath + - ") to " + outDexFile); - final long callingId = Binder.clearCallingIdentity(); - try { - synchronized (mInstallLock) { - return mInstaller.compileLayouts(apkPath, packageName, outDexFile, - ps.getAppId()); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } catch (Throwable e) { - Log.e("PackageManager", "Failed to compile layouts", e); - return false; - } - } -} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b4396818c43c..077812b49cdd 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -546,6 +546,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mLidKeyboardAccessibility; int mLidNavigationAccessibility; int mShortPressOnPowerBehavior; + private boolean mShouldEarlyShortPressOnPower; int mLongPressOnPowerBehavior; long mLongPressOnPowerAssistantTimeoutMs; int mVeryLongPressOnPowerBehavior; @@ -2651,6 +2652,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onPress(long downTime) { + if (mShouldEarlyShortPressOnPower) { + return; + } powerPress(downTime, 1 /*count*/); } @@ -2684,6 +2688,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { void onMultiPress(long downTime, int count) { powerPress(downTime, count); } + + @Override + void onKeyUp(long eventTime, int count) { + if (mShouldEarlyShortPressOnPower && count == 1) { + powerPress(eventTime, 1 /*pressCount*/); + } + } } /** @@ -2913,6 +2924,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS, mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior)); + mShouldEarlyShortPressOnPower = + mContext.getResources() + .getBoolean(com.android.internal.R.bool.config_shortPressEarlyOnPower); mStylusButtonsEnabled = Settings.Secure.getIntForUser(resolver, Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1; diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 88c2e095453d..dd39fb02573e 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -34,7 +34,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; @@ -256,10 +256,11 @@ public final class HintManagerService extends SystemService { @VisibleForTesting final class MyUidObserver extends UidObserver { - private final SparseArray<Integer> mProcStatesCache = new SparseArray<>(); - + private final Object mCacheLock = new Object(); + @GuardedBy("mCacheLock") + private final SparseIntArray mProcStatesCache = new SparseIntArray(); public boolean isUidForeground(int uid) { - synchronized (mLock) { + synchronized (mCacheLock) { return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } @@ -268,6 +269,9 @@ public final class HintManagerService extends SystemService { @Override public void onUidGone(int uid, boolean disabled) { FgThread.getHandler().post(() -> { + synchronized (mCacheLock) { + mProcStatesCache.delete(uid); + } synchronized (mLock) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid); if (tokenMap == null) { @@ -280,7 +284,6 @@ public final class HintManagerService extends SystemService { sessionSet.valueAt(j).close(); } } - mProcStatesCache.delete(uid); } }); } @@ -292,15 +295,18 @@ public final class HintManagerService extends SystemService { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { FgThread.getHandler().post(() -> { - synchronized (mLock) { + synchronized (mCacheLock) { mProcStatesCache.put(uid, procState); + } + boolean shouldAllowUpdate = isUidForeground(uid); + synchronized (mLock) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid); if (tokenMap == null) { return; } for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) { for (AppHintSession s : sessionSet) { - s.onProcStateChanged(); + s.onProcStateChanged(shouldAllowUpdate); } } } @@ -429,10 +435,10 @@ public final class HintManagerService extends SystemService { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { return; } + pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); + pw.println("HAL Support: " + isHalSupported()); + pw.println("Active Sessions:"); synchronized (mLock) { - pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); - pw.println("HAL Support: " + isHalSupported()); - pw.println("Active Sessions:"); for (int i = 0; i < mActiveSessions.size(); i++) { pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":"); ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = @@ -476,7 +482,8 @@ public final class HintManagerService extends SystemService { mHalSessionPtr = halSessionPtr; mTargetDurationNanos = durationNanos; mUpdateAllowed = true; - updateHintAllowed(); + final boolean allowed = mUidObserver.isUidForeground(mUid); + updateHintAllowed(allowed); try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -486,9 +493,8 @@ public final class HintManagerService extends SystemService { } @VisibleForTesting - boolean updateHintAllowed() { - synchronized (mLock) { - final boolean allowed = mUidObserver.isUidForeground(mUid); + boolean updateHintAllowed(boolean allowed) { + synchronized (this) { if (allowed && !mUpdateAllowed) resume(); if (!allowed && mUpdateAllowed) pause(); mUpdateAllowed = allowed; @@ -498,8 +504,8 @@ public final class HintManagerService extends SystemService { @Override public void updateTargetWorkDuration(long targetDurationNanos) { - synchronized (mLock) { - if (mHalSessionPtr == 0 || !updateHintAllowed()) { + synchronized (this) { + if (mHalSessionPtr == 0 || !mUpdateAllowed) { return; } Preconditions.checkArgument(targetDurationNanos > 0, "Expected" @@ -511,8 +517,8 @@ public final class HintManagerService extends SystemService { @Override public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) { - synchronized (mLock) { - if (mHalSessionPtr == 0 || !updateHintAllowed()) { + synchronized (this) { + if (mHalSessionPtr == 0 || !mUpdateAllowed) { return; } Preconditions.checkArgument(actualDurationNanos.length != 0, "the count" @@ -534,11 +540,13 @@ public final class HintManagerService extends SystemService { /** TODO: consider monitor session threads and close session if any thread is dead. */ @Override public void close() { - synchronized (mLock) { + synchronized (this) { if (mHalSessionPtr == 0) return; mNativeWrapper.halCloseHintSession(mHalSessionPtr); mHalSessionPtr = 0; mToken.unlinkToDeath(this, 0); + } + synchronized (mLock) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid); if (tokenMap == null) { Slogf.w(TAG, "UID %d is not present in active session map", mUid); @@ -557,8 +565,8 @@ public final class HintManagerService extends SystemService { @Override public void sendHint(@PerformanceHintManager.Session.Hint int hint) { - synchronized (mLock) { - if (mHalSessionPtr == 0 || !updateHintAllowed()) { + synchronized (this) { + if (mHalSessionPtr == 0 || !mUpdateAllowed) { return; } Preconditions.checkArgument(hint >= 0, "the hint ID value should be" @@ -568,7 +576,7 @@ public final class HintManagerService extends SystemService { } public void setThreads(@NonNull int[] tids) { - synchronized (mLock) { + synchronized (this) { if (mHalSessionPtr == 0) { return; } @@ -588,7 +596,7 @@ public final class HintManagerService extends SystemService { } finally { Binder.restoreCallingIdentity(identity); } - if (!updateHintAllowed()) { + if (!mUpdateAllowed) { Slogf.v(TAG, "update hint not allowed, storing tids."); mNewThreadIds = tids; return; @@ -599,13 +607,15 @@ public final class HintManagerService extends SystemService { } public int[] getThreadIds() { - return mThreadIds; + synchronized (this) { + return Arrays.copyOf(mThreadIds, mThreadIds.length); + } } @Override public void setMode(int mode, boolean enabled) { - synchronized (mLock) { - if (mHalSessionPtr == 0 || !updateHintAllowed()) { + synchronized (this) { + if (mHalSessionPtr == 0 || !mUpdateAllowed) { return; } Preconditions.checkArgument(mode >= 0, "the mode Id value should be" @@ -614,19 +624,19 @@ public final class HintManagerService extends SystemService { } } - private void onProcStateChanged() { - updateHintAllowed(); + private void onProcStateChanged(boolean updateAllowed) { + updateHintAllowed(updateAllowed); } private void pause() { - synchronized (mLock) { + synchronized (this) { if (mHalSessionPtr == 0) return; mNativeWrapper.halPauseHintSession(mHalSessionPtr); } } private void resume() { - synchronized (mLock) { + synchronized (this) { if (mHalSessionPtr == 0) return; mNativeWrapper.halResumeHintSession(mHalSessionPtr); if (mNewThreadIds != null) { @@ -638,12 +648,12 @@ public final class HintManagerService extends SystemService { } private void dump(PrintWriter pw, String prefix) { - synchronized (mLock) { + synchronized (this) { pw.println(prefix + "SessionPID: " + mPid); pw.println(prefix + "SessionUID: " + mUid); pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds)); pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos); - pw.println(prefix + "SessionAllowed: " + updateHintAllowed()); + pw.println(prefix + "SessionAllowed: " + mUpdateAllowed); } } diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING new file mode 100644 index 000000000000..10c53624b7ee --- /dev/null +++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "postsubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.power.hint" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index fd8f6bfd1cc8..bdab4d483872 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9317,8 +9317,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); final TaskFragment organizedTf = getOrganizedTaskFragment(); + float aspectRatioToApply = desiredAspectRatio; if (task == null || rootTask == null - || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1) + || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1) // Don't set aspect ratio if we are in VR mode. || isInVrUiMode(getConfiguration()) // TODO(b/232898850): Always respect aspect ratio requests. @@ -9331,30 +9332,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int containingAppHeight = containingAppBounds.height(); final float containingRatio = computeAspectRatio(containingAppBounds); - if (desiredAspectRatio < 1) { - desiredAspectRatio = containingRatio; + if (aspectRatioToApply < 1) { + aspectRatioToApply = containingRatio; } - if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) { - desiredAspectRatio = maxAspectRatio; - } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) { - desiredAspectRatio = minAspectRatio; + if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) { + aspectRatioToApply = maxAspectRatio; + } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) { + aspectRatioToApply = minAspectRatio; } int activityWidth = containingAppWidth; int activityHeight = containingAppHeight; - if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) { + if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) { if (containingAppWidth < containingAppHeight) { // Width is the shorter side, so we use that to figure-out what the max. height // should be given the aspect ratio. - activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f); + activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f); } else { // Height is the shorter side, so we use that to figure-out what the max. width // should be given the aspect ratio. - activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f); + activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f); } - } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) { + } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) { boolean adjustWidth; switch (getRequestedConfigurationOrientation()) { case ORIENTATION_LANDSCAPE: @@ -9382,9 +9383,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A break; } if (adjustWidth) { - activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f); + activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f); } else { - activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f); + activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f); } } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index e97bda218dcd..1e4b258ff5c5 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; -import static com.android.internal.util.Preconditions.checkState; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -40,7 +39,6 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AppOpsManager; import android.app.BackgroundStartPrivileges; -import android.app.ComponentOptions; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -73,14 +71,18 @@ public class BackgroundActivityStartController { private static final String TAG = TAG_WITH_CLASS_NAME ? "BackgroundActivityStartController" : TAG_ATM; - public static final String VERDICT_ALLOWED = "Activity start allowed"; - public static final String VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL = - "Activity start would be allowed if the sender granted BAL privileges"; private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS; private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5; + public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED = + ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); private final ActivityTaskManagerService mService; + private final ActivityTaskSupervisor mSupervisor; // TODO(b/263368846) Rename when ASM logic is moved in @@ -205,9 +207,130 @@ public class BackgroundActivityStartController { backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK; } + private class BalState { + + private final String mCallingPackage; + private final int mCallingUid; + private final int mCallingPid; + private final @ActivityTaskManagerService.AppSwitchState int mAppSwitchState; + private final boolean mCallingUidHasAnyVisibleWindow; + private final @ActivityManager.ProcessState int mCallingUidProcState; + private final boolean mIsCallingUidPersistentSystemProcess; + private final BackgroundStartPrivileges mBalAllowedByPiSender; + private final BackgroundStartPrivileges mBalAllowedByPiCreator; + private final String mRealCallingPackage; + private final int mRealCallingUid; + private final int mRealCallingPid; + private final boolean mRealCallingUidHasAnyVisibleWindow; + private final @ActivityManager.ProcessState int mRealCallingUidProcState; + private final boolean mIsRealCallingUidPersistentSystemProcess; + private final PendingIntentRecord mOriginatingPendingIntent; + private final BackgroundStartPrivileges mBackgroundStartPrivileges; + private final Intent mIntent; + private final WindowProcessController mCallerApp; + private final WindowProcessController mRealCallerApp; + + private BalState(int callingUid, int callingPid, final String callingPackage, + int realCallingUid, int realCallingPid, + WindowProcessController callerApp, + PendingIntentRecord originatingPendingIntent, + BackgroundStartPrivileges backgroundStartPrivileges, + Intent intent, + ActivityOptions checkedOptions) { + this.mCallingPackage = callingPackage; + mCallingUid = callingUid; + mCallingPid = callingPid; + mRealCallingUid = realCallingUid; + mRealCallingPid = realCallingPid; + mCallerApp = callerApp; + mBackgroundStartPrivileges = backgroundStartPrivileges; + mOriginatingPendingIntent = originatingPendingIntent; + mIntent = intent; + mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); + mBalAllowedByPiSender = + PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(checkedOptions, + realCallingUid, mRealCallingPackage); + mBalAllowedByPiCreator = + checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED + ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; + mAppSwitchState = mService.getBalAppSwitchesState(); + mCallingUidProcState = mService.mActiveUids.getUidState(callingUid); + mIsCallingUidPersistentSystemProcess = + mCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; + mCallingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid); + if (callingUid == realCallingUid) { + mRealCallingUidProcState = mCallingUidProcState; + mRealCallingUidHasAnyVisibleWindow = mCallingUidHasAnyVisibleWindow; + // In the PendingIntent case callerApp is not passed in, so resolve it ourselves. + mRealCallerApp = callerApp == null + ? mService.getProcessController(realCallingPid, realCallingUid) + : callerApp; + mIsRealCallingUidPersistentSystemProcess = mIsCallingUidPersistentSystemProcess; + } else { + mRealCallingUidProcState = mService.mActiveUids.getUidState(realCallingUid); + mRealCallingUidHasAnyVisibleWindow = + mService.hasActiveVisibleWindow(realCallingUid); + mRealCallerApp = mService.getProcessController(realCallingPid, realCallingUid); + mIsRealCallingUidPersistentSystemProcess = + mRealCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; + } + } + + private String getDebugPackageName(String packageName, int uid) { + if (packageName != null) { + return packageName; // use actual package + } + if (uid == 0) { + return "root[debugOnly]"; + } + String name = mService.mContext.getPackageManager().getNameForUid(uid); + if (name == null) { + name = "uid=" + uid; + } + return name + "[debugOnly]"; + } + + private String dump(@BalCode int mResultIfPiCreatorAllowsBal, + @BalCode int mResultIfPiSenderAllowsBal) { + return " [callingPackage: " + getDebugPackageName(mCallingPackage, mCallingUid) + + "; callingUid: " + mCallingUid + + "; appSwitchState: " + mAppSwitchState + + "; callingUidHasAnyVisibleWindow: " + mCallingUidHasAnyVisibleWindow + + "; callingUidProcState: " + DebugUtils.valueToString( + ActivityManager.class, "PROCESS_STATE_", mCallingUidProcState) + + "; isCallingUidPersistentSystemProcess: " + + mIsCallingUidPersistentSystemProcess + + "; balAllowedByPiCreator: " + mBalAllowedByPiCreator + + "; balAllowedByPiSender: " + mBalAllowedByPiSender + + "; realCallingPackage: " + + getDebugPackageName(mRealCallingPackage, mRealCallingUid) + + "; realCallingUid: " + mRealCallingUid + + "; realCallingPid: " + mRealCallingPid + + "; realCallingUidHasAnyVisibleWindow: " + mRealCallingUidHasAnyVisibleWindow + + "; realCallingUidProcState: " + DebugUtils.valueToString( + ActivityManager.class, "PROCESS_STATE_", mRealCallingUidProcState) + + "; isRealCallingUidPersistentSystemProcess: " + + mIsRealCallingUidPersistentSystemProcess + + "; originatingPendingIntent: " + mOriginatingPendingIntent + + "; backgroundStartPrivileges: " + mBackgroundStartPrivileges + + "; intent: " + mIntent + + "; callerApp: " + mCallerApp + + "; realCallerApp: " + mRealCallerApp + + "; inVisibleTask: " + + (mCallerApp != null && mCallerApp.hasActivityInVisibleTask()) + + "; realInVisibleTask: " + + (mRealCallerApp != null && mRealCallerApp.hasActivityInVisibleTask()) + + "; resultIfPiSenderAllowsBal: " + balCodeToString(mResultIfPiSenderAllowsBal) + + "; resultIfPiCreatorAllowsBal: " + + balCodeToString(mResultIfPiCreatorAllowsBal) + + "]"; + } + } + /** * @return A code denoting which BAL rule allows an activity to be started, - * or {@link BAL_BLOCK} if the launch should be blocked + * or {@link #BAL_BLOCK} if the launch should be blocked */ @BalCode int checkBackgroundActivityStart( @@ -221,36 +344,136 @@ public class BackgroundActivityStartController { BackgroundStartPrivileges backgroundStartPrivileges, Intent intent, ActivityOptions checkedOptions) { - // don't abort for the most important UIDs - final int callingAppId = UserHandle.getAppId(callingUid); - final boolean useCallingUidState = - originatingPendingIntent == null - || checkedOptions == null - || checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() - != ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; - if (useCallingUidState) { - if (callingUid == Process.ROOT_UID - || callingAppId == Process.SYSTEM_UID - || callingAppId == Process.NFC_UID) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, - callingUid, realCallingUid, intent, "Important callingUid"); - } - // Always allow home application to start activities. - if (isHomeApp(callingUid, callingPackage)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, callingUid, realCallingUid, intent, - "Home app"); + if (checkedOptions == null) { + // replace null with a constant to simplify evaluation + checkedOptions = ACTIVITY_OPTIONS_SYSTEM_DEFINED; + } + + BalState state = new BalState(callingUid, callingPid, callingPackage, + realCallingUid, realCallingPid, callerApp, originatingPendingIntent, + backgroundStartPrivileges, intent, checkedOptions); + + // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a + // visible window. + if (Process.isSdkSandboxUid(state.mRealCallingUid)) { + int realCallingSdkSandboxUidToAppUid = + Process.getAppUidForSdkSandboxUid(state.mRealCallingUid); + // realCallingSdkSandboxUidToAppUid should probably just be used instead (or in addition + // to realCallingUid when calculating resultForRealCaller below. + if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX, + /*background*/ false, state, + "uid in SDK sandbox has visible (non-toast) window"); } + } - // IME should always be allowed to start activity, like IME settings. - final WindowState imeWindow = - mService.mRootWindowContainer.getCurrentInputMethodWindow(); - if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, callingUid, realCallingUid, intent, - "Active ime"); + @BalCode int resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); + @BalCode int resultForRealCaller = callingUid == realCallingUid + ? resultForCaller // no need to calculate again + : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); + + if (resultForCaller != BAL_BLOCK + && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { + if (DEBUG_ACTIVITY_STARTS) { + Slog.d(TAG, "Activity start explicitly allowed by PI creator. " + + state.dump(resultForCaller, resultForRealCaller)); } + return resultForCaller; + } + if (resultForRealCaller != BAL_BLOCK + && checkedOptions.getPendingIntentBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { + if (DEBUG_ACTIVITY_STARTS) { + Slog.i(TAG, "Activity start explicitly allowed by PI sender. " + + state.dump(resultForCaller, resultForRealCaller)); + } + return resultForRealCaller; + } + if (resultForCaller != BAL_BLOCK + && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + // Allowed before V by creator + Slog.wtf(TAG, + "With Android 15 BAL hardening this activity start would be blocked" + + " (missing opt in by PI creator)! " + + state.dump(resultForCaller, resultForRealCaller)); + return resultForCaller; + } + if (resultForRealCaller != BAL_BLOCK + && checkedOptions.getPendingIntentBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + // Allowed before U by sender + if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) { + Slog.wtf(TAG, + "With Android 14 BAL hardening this activity start would be blocked" + + " (missing opt in by PI sender)! " + + state.dump(resultForCaller, resultForRealCaller)); + return resultForRealCaller; + } + Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" + + " (missing opt in by PI sender)! " + + state.dump(resultForCaller, resultForRealCaller)); + // fall through + } + // anything that has fallen through would currently be aborted + Slog.w(TAG, "Background activity launch blocked" + + state.dump(resultForCaller, resultForRealCaller)); + // log aborted activity start to TRON + if (mService.isActivityStartsLoggingEnabled()) { + mSupervisor + .getActivityMetricsLogger() + .logAbortedBgActivityStart( + intent, + callerApp, + callingUid, + callingPackage, + state.mCallingUidProcState, + state.mCallingUidHasAnyVisibleWindow, + realCallingUid, + state.mRealCallingUidProcState, + state.mRealCallingUidHasAnyVisibleWindow, + (originatingPendingIntent != null)); + } + return BAL_BLOCK; + } + + /** + * @return A code denoting which BAL rule allows an activity to be started, + * or {@link #BAL_BLOCK} if the launch should be blocked + */ + @BalCode + int checkBackgroundActivityStartAllowedByCaller(BalState state) { + int callingUid = state.mCallingUid; + int callingPid = state.mCallingPid; + final String callingPackage = state.mCallingPackage; + WindowProcessController callerApp = state.mCallerApp; + + // don't abort for the most important UIDs + final int callingAppId = UserHandle.getAppId(callingUid); + if (callingUid == Process.ROOT_UID + || callingAppId == Process.SYSTEM_UID + || callingAppId == Process.NFC_UID) { + return logStartAllowedAndReturnCode( + BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, + state, "Important callingUid"); + } + + // Always allow home application to start activities. + if (isHomeApp(callingUid, callingPackage)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ false, state, + "Home app"); + } + + // IME should always be allowed to start activity, like IME settings. + final WindowState imeWindow = + mService.mRootWindowContainer.getCurrentInputMethodWindow(); + if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ false, state, + "Active ime"); } // This is used to block background activity launch even if the app is still @@ -269,329 +492,171 @@ public class BackgroundActivityStartController { appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY; final boolean allowCallingUidStartActivity = ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) - && callingUidHasAnyVisibleWindow) + && callingUidHasAnyVisibleWindow) || isCallingUidPersistentSystemProcess; - if (useCallingUidState && allowCallingUidStartActivity) { + if (allowCallingUidStartActivity) { return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, callingUid, realCallingUid, intent, + /*background*/ false, state, "callingUidHasAnyVisibleWindow = " + callingUid + ", isCallingUidPersistentSystemProcess = " + isCallingUidPersistentSystemProcess); } - // take realCallingUid into consideration - final int realCallingUidProcState = - (callingUid == realCallingUid) - ? callingUidProcState - : mService.mActiveUids.getUidState(realCallingUid); - final boolean realCallingUidHasAnyVisibleWindow = - (callingUid == realCallingUid) - ? callingUidHasAnyVisibleWindow - : mService.hasActiveVisibleWindow(realCallingUid); - final int realCallingAppId = UserHandle.getAppId(realCallingUid); - final boolean isRealCallingUidPersistentSystemProcess = - (callingUid == realCallingUid) - ? isCallingUidPersistentSystemProcess - : (realCallingAppId == Process.SYSTEM_UID) - || realCallingUidProcState - <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; - // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a - // visible window. - if (Process.isSdkSandboxUid(realCallingUid)) { - int realCallingSdkSandboxUidToAppUid = - Process.getAppUidForSdkSandboxUid(realCallingUid); - - if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX, - /*background*/ false, callingUid, realCallingUid, intent, - "uid in SDK sandbox has visible (non-toast) window"); - } - } - - String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); - - // Legacy behavior allows to use caller foreground state to bypass BAL restriction. - // The options here are the options passed by the sender and not those on the intent. - final BackgroundStartPrivileges balAllowedByPiSender = - PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller( - checkedOptions, realCallingUid, realCallingPackage); - - final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null - || checkedOptions.getPendingIntentBackgroundActivityStartMode() - == ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; - final boolean considerPiRules = logVerdictChangeByPiDefaultChange - || balAllowedByPiSender.allowsBackgroundActivityStarts(); - final String verdictLogForPiSender = - balAllowedByPiSender.allowsBackgroundActivityStarts() ? VERDICT_ALLOWED - : VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL; - - @BalCode int resultIfPiSenderAllowsBal = BAL_BLOCK; - if (realCallingUid != callingUid && considerPiRules) { - resultIfPiSenderAllowsBal = checkPiBackgroundActivityStart(callingUid, realCallingUid, - backgroundStartPrivileges, intent, checkedOptions, - realCallingUidHasAnyVisibleWindow, isRealCallingUidPersistentSystemProcess, - verdictLogForPiSender); - } - if (resultIfPiSenderAllowsBal != BAL_BLOCK - && balAllowedByPiSender.allowsBackgroundActivityStarts() - && !logVerdictChangeByPiDefaultChange) { - // The result is to allow (because the sender allows BAL) and we are not interested in - // logging differences, so just return. - return resultIfPiSenderAllowsBal; - } - if (useCallingUidState) { - // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission - if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, - callingPid, callingUid) == PERMISSION_GRANTED) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, intent, + // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission + if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, + callingPid, callingUid) == PERMISSION_GRANTED) { + return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, + /*background*/ true, state, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); - } - // don't abort if the caller has the same uid as the recents component - if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { - return logStartAllowedAndReturnCode( - BAL_ALLOW_ALLOWLISTED_COMPONENT, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, - intent, "Recents Component"); - } - // don't abort if the callingUid is the device owner - if (mService.isDeviceOwner(callingUid)) { - return logStartAllowedAndReturnCode( - BAL_ALLOW_ALLOWLISTED_COMPONENT, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, - intent, "Device Owner"); - } - // don't abort if the callingUid is a affiliated profile owner - if (mService.isAffiliatedProfileOwner(callingUid)) { - return logStartAllowedAndReturnCode( - BAL_ALLOW_ALLOWLISTED_COMPONENT, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, - intent, "Affiliated Profile Owner"); - } - // don't abort if the callingUid has companion device - final int callingUserId = UserHandle.getUserId(callingUid); - if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { - return logStartAllowedAndReturnCode( - BAL_ALLOW_ALLOWLISTED_COMPONENT, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, - intent, "Companion App"); - } - // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission - if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { - Slog.w( - TAG, - "Background activity start for " - + callingPackage - + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); - return logStartAllowedAndReturnCode( - BAL_ALLOW_SAW_PERMISSION, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, - intent, "SYSTEM_ALERT_WINDOW permission is granted"); - } - // don't abort if the callingUid and callingPackage have the - // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop - if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow( - AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, - callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, intent, - "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); - } } - // If we don't have callerApp at this point, no caller was provided to startActivity(). - // That's the case for PendingIntent-based starts, since the creator's process might not be - // up and alive. If that's the case, we retrieve the WindowProcessController for the send() - // caller if caller allows, so that we can make the decision based on its state. - int callerAppUid = callingUid; - boolean callerAppBasedOnPiSender = callerApp == null && considerPiRules - && resultIfPiSenderAllowsBal == BAL_BLOCK; - if (callerAppBasedOnPiSender) { - callerApp = mService.getProcessController(realCallingPid, realCallingUid); - callerAppUid = realCallingUid; + // don't abort if the caller has the same uid as the recents component + if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, state, "Recents Component"); } - // don't abort if the callerApp or other processes of that uid are allowed in any way - if (callerApp != null && (useCallingUidState || callerAppBasedOnPiSender)) { - // first check the original calling process - final @BalCode int balAllowedForCaller = callerApp - .areBackgroundActivityStartsAllowed(appSwitchState); - if (balAllowedForCaller != BAL_BLOCK) { - if (callerAppBasedOnPiSender) { - resultIfPiSenderAllowsBal = logStartAllowedAndReturnCode(balAllowedForCaller, - /*background*/ true, callingUid, realCallingUid, intent, - "callerApp process (pid = " + callerApp.getPid() - + ", uid = " + callerAppUid + ") is allowed", verdictLogForPiSender); - } else { - return logStartAllowedAndReturnCode(balAllowedForCaller, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, intent, - "callerApp process (pid = " + callerApp.getPid() - + ", uid = " + callerAppUid + ") is allowed"); - } - } else { - // only if that one wasn't allowed, check the other ones - final ArraySet<WindowProcessController> uidProcesses = - mService.mProcessMap.getProcesses(callerAppUid); - if (uidProcesses != null) { - for (int i = uidProcesses.size() - 1; i >= 0; i--) { - final WindowProcessController proc = uidProcesses.valueAt(i); - int balAllowedForUid = proc.areBackgroundActivityStartsAllowed( - appSwitchState); - if (proc != callerApp && balAllowedForUid != BAL_BLOCK) { - if (callerAppBasedOnPiSender) { - resultIfPiSenderAllowsBal = logStartAllowedAndReturnCode( - balAllowedForUid, - /*background*/ true, callingUid, realCallingUid, intent, - "process" + proc.getPid() + " from uid " + callerAppUid - + " is allowed", verdictLogForPiSender); - break; - } else { - return logStartAllowedAndReturnCode(balAllowedForUid, - resultIfPiSenderAllowsBal, balAllowedByPiSender, - /*background*/ true, callingUid, realCallingUid, intent, - "process" + proc.getPid() + " from uid " + callerAppUid - + " is allowed"); - } - } - } - } - } - if (callerAppBasedOnPiSender) { - // If caller app was based on PI sender, this result is part of - // resultIfPiSenderAllowsBal - if (resultIfPiSenderAllowsBal != BAL_BLOCK - && balAllowedByPiSender.allowsBackgroundActivityStarts() - && !logVerdictChangeByPiDefaultChange) { - // The result is to allow (because the sender allows BAL) and we are not - // interested in logging differences, so just return. - return resultIfPiSenderAllowsBal; - } - } else { - // If caller app was NOT based on PI sender and we found a allow reason we should - // have returned already - checkState(balAllowedForCaller == BAL_BLOCK, - "balAllowedForCaller = " + balAllowedForCaller + " (should have returned)"); - } + // don't abort if the callingUid is the device owner + if (mService.isDeviceOwner(callingUid)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, state, "Device Owner"); } - // If we are here, it means all exemptions not based on PI sender failed, so we'll block - // unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL - - if (realCallingPackage == null) { - realCallingPackage = (callingUid == realCallingUid ? callingPackage : - mService.mContext.getPackageManager().getNameForUid(realCallingUid)) - + "[debugOnly]"; - } - - String stateDumpLog = " [callingPackage: " + callingPackage - + "; callingUid: " + callingUid - + "; appSwitchState: " + appSwitchState - + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow - + "; callingUidProcState: " + DebugUtils.valueToString( - ActivityManager.class, "PROCESS_STATE_", callingUidProcState) - + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess - + "; balAllowedByPiSender: " + balAllowedByPiSender - + "; realCallingPackage: " + realCallingPackage - + "; realCallingUid: " + realCallingUid - + "; realCallingUidHasAnyVisibleWindow: " + realCallingUidHasAnyVisibleWindow - + "; realCallingUidProcState: " + DebugUtils.valueToString( - ActivityManager.class, "PROCESS_STATE_", realCallingUidProcState) - + "; isRealCallingUidPersistentSystemProcess: " - + isRealCallingUidPersistentSystemProcess - + "; originatingPendingIntent: " + originatingPendingIntent - + "; backgroundStartPrivileges: " + backgroundStartPrivileges - + "; intent: " + intent - + "; callerApp: " + callerApp - + "; inVisibleTask: " + (callerApp != null && callerApp.hasActivityInVisibleTask()) - + "; resultIfPiSenderAllowsBal: " + balCodeToString(resultIfPiSenderAllowsBal) - + "]"; - if (resultIfPiSenderAllowsBal != BAL_BLOCK) { - // We should have returned before if !logVerdictChangeByPiDefaultChange - checkState(logVerdictChangeByPiDefaultChange, - "resultIfPiSenderAllowsBal = " + balCodeToString(resultIfPiSenderAllowsBal) - + " at the end but logVerdictChangeByPiDefaultChange = false"); - if (balAllowedByPiSender.allowsBackgroundActivityStarts()) { - // The verdict changed from block to allow, PI sender default change is off and - // we'd block if it were on - Slog.wtf(TAG, "With BAL hardening this activity start would be blocked!" - + stateDumpLog); - return resultIfPiSenderAllowsBal; - } else { - // The verdict changed from allow (resultIfPiSenderAllowsBal) to block, PI sender - // default change is on (otherwise we would have fallen into if above) and we'd - // allow if it were off - Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed!" - + stateDumpLog); - } + // don't abort if the callingUid is a affiliated profile owner + if (mService.isAffiliatedProfileOwner(callingUid)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, state, "Affiliated Profile Owner"); } - // anything that has fallen through would currently be aborted - Slog.w(TAG, "Background activity launch blocked" + stateDumpLog); - // log aborted activity start to TRON - if (mService.isActivityStartsLoggingEnabled()) { - mSupervisor - .getActivityMetricsLogger() - .logAbortedBgActivityStart( - intent, - callerApp, - callingUid, - callingPackage, - callingUidProcState, - callingUidHasAnyVisibleWindow, - realCallingUid, - realCallingUidProcState, - realCallingUidHasAnyVisibleWindow, - (originatingPendingIntent != null)); + // don't abort if the callingUid has companion device + final int callingUserId = UserHandle.getUserId(callingUid); + if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { + return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, state, "Companion App"); + } + // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission + if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { + Slog.w( + TAG, + "Background activity start for " + + callingPackage + + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); + return logStartAllowedAndReturnCode(BAL_ALLOW_SAW_PERMISSION, + /*background*/ true, state, "SYSTEM_ALERT_WINDOW permission is granted"); + } + // don't abort if the callingUid and callingPackage have the + // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop + if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, + callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) { + return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, + /*background*/ true, state, + "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); + } + + // If we don't have callerApp at this point, no caller was provided to startActivity(). + // That's the case for PendingIntent-based starts, since the creator's process might not be + // up and alive. + // Don't abort if the callerApp or other processes of that uid are allowed in any way. + @BalCode int callerAppAllowsBal = checkProcessAllowsBal(callerApp, state); + if (callerAppAllowsBal != BAL_BLOCK) { + return callerAppAllowsBal; } + + // If we are here, it means all exemptions based on the creator failed return BAL_BLOCK; } - private @BalCode int checkPiBackgroundActivityStart(int callingUid, int realCallingUid, - BackgroundStartPrivileges backgroundStartPrivileges, Intent intent, - ActivityOptions checkedOptions, boolean realCallingUidHasAnyVisibleWindow, - boolean isRealCallingUidPersistentSystemProcess, String verdictLog) { - final boolean useCallerPermission = - PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions); - if (useCallerPermission + /** + * @return A code denoting which BAL rule allows an activity to be started, + * or {@link #BAL_BLOCK} if the launch should be blocked + */ + @BalCode + int checkBackgroundActivityStartAllowedBySender( + BalState state, + ActivityOptions checkedOptions) { + int realCallingUid = state.mRealCallingUid; + + if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions) && ActivityManager.checkComponentPermission( - android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, realCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, callingUid, realCallingUid, intent, - "realCallingUid has BAL permission. realCallingUid: " + realCallingUid, - verdictLog); + /*background*/ false, state, + "realCallingUid has BAL permission. realCallingUid: " + realCallingUid); } // don't abort if the realCallingUid has a visible window // TODO(b/171459802): We should check appSwitchAllowed also - if (realCallingUidHasAnyVisibleWindow) { + if (state.mRealCallingUidHasAnyVisibleWindow) { return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, callingUid, realCallingUid, intent, + /*background*/ false, state, "realCallingUid has visible (non-toast) window. realCallingUid: " - + realCallingUid, verdictLog); + + realCallingUid); } // if the realCallingUid is a persistent system process, abort if the IntentSender // wasn't allowed to start an activity - if (isRealCallingUidPersistentSystemProcess - && backgroundStartPrivileges.allowsBackgroundActivityStarts()) { + if (state.mIsRealCallingUidPersistentSystemProcess + && state.mBackgroundStartPrivileges.allowsBackgroundActivityStarts()) { return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, callingUid, realCallingUid, intent, + /*background*/ false, state, "realCallingUid is persistent system process AND intent " + "sender allowed (allowBackgroundActivityStart = true). " - + "realCallingUid: " + realCallingUid, verdictLog); + + "realCallingUid: " + realCallingUid); } // don't abort if the realCallingUid is an associated companion app if (mService.isAssociatedCompanionApp( UserHandle.getUserId(realCallingUid), realCallingUid)) { return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, callingUid, realCallingUid, intent, + /*background*/ false, state, "realCallingUid is a companion app. " - + "realCallingUid: " + realCallingUid, verdictLog); + + "realCallingUid: " + realCallingUid); + } + + // don't abort if the callerApp or other processes of that uid are allowed in any way + @BalCode int realCallerAppAllowsBal = + checkProcessAllowsBal(state.mRealCallerApp, state); + if (realCallerAppAllowsBal != BAL_BLOCK) { + return realCallerAppAllowsBal; + } + + // If we are here, it means all exemptions based on PI sender failed + return BAL_BLOCK; + } + + /** + * Check if the app allows BAL. + * + * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int, + * String, int, boolean, boolean, boolean, long, long, long)} for details on the + * exceptions. + */ + private @BalCode int checkProcessAllowsBal(WindowProcessController app, BalState state) { + if (app == null) { + return BAL_BLOCK; + } + // first check the original calling process + final @BalCode int balAllowedForCaller = app + .areBackgroundActivityStartsAllowed(state.mAppSwitchState); + if (balAllowedForCaller != BAL_BLOCK) { + return logStartAllowedAndReturnCode(balAllowedForCaller, + /*background*/ true, state, + "callerApp process (pid = " + app.getPid() + + ", uid = " + app.mUid + ") is allowed"); + } else { + // only if that one wasn't allowed, check the other ones + final ArraySet<WindowProcessController> uidProcesses = + mService.mProcessMap.getProcesses(app.mUid); + if (uidProcesses != null) { + for (int i = uidProcesses.size() - 1; i >= 0; i--) { + final WindowProcessController proc = uidProcesses.valueAt(i); + int balAllowedForUid = proc.areBackgroundActivityStartsAllowed( + state.mAppSwitchState); + if (proc != app && balAllowedForUid != BAL_BLOCK) { + return logStartAllowedAndReturnCode(balAllowedForUid, + /*background*/ true, state, + "process" + proc.getPid() + " from uid " + app.mUid + + " is allowed"); + } + } + } } return BAL_BLOCK; } @@ -1091,51 +1156,32 @@ public class BackgroundActivityStartController { return joiner.toString(); } - static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background, - int callingUid, int realCallingUid, Intent intent, int pid, String msg) { - return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent, - DEBUG_ACTIVITY_STARTS ? ("[Process(" + pid + ")]" + msg) : ""); + static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, + boolean background, int callingUid, int realCallingUid, Intent intent, int pid, + String msg) { + return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, + intent, DEBUG_ACTIVITY_STARTS ? ("[Process(" + pid + ")]" + msg) : ""); } - static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background, - int callingUid, int realCallingUid, Intent intent, String msg) { - return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent, - msg, VERDICT_ALLOWED); + private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, + boolean background, BalState state, String msg) { + return logStartAllowedAndReturnCode(code, background, state.mCallingUid, + state.mRealCallingUid, state.mIntent, msg); } - /** - * Logs the start and returns one of the provided codes depending on if the PI sender allows - * using its BAL privileges. - */ - static @BalCode int logStartAllowedAndReturnCode(@BalCode int result, - @BalCode int resultIfPiSenderAllowsBal, BackgroundStartPrivileges balAllowedByPiSender, + private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background, int callingUid, int realCallingUid, Intent intent, String msg) { - if (resultIfPiSenderAllowsBal != BAL_BLOCK - && balAllowedByPiSender.allowsBackgroundActivityStarts()) { - // resultIfPiSenderAllowsBal was already logged, so just return - return resultIfPiSenderAllowsBal; - } - return logStartAllowedAndReturnCode(result, background, callingUid, realCallingUid, - intent, msg, VERDICT_ALLOWED); - } - - - static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background, - int callingUid, int realCallingUid, Intent intent, String msg, String verdict) { statsLogBalAllowed(code, callingUid, realCallingUid, intent); if (DEBUG_ACTIVITY_STARTS) { StringBuilder builder = new StringBuilder(); if (background) { builder.append("Background "); } - builder.append(verdict + ": " + msg + ". callingUid: " + callingUid + ". "); + builder.append("Activity start allowed: " + msg + ". callingUid: " + + callingUid + ". "); builder.append("BAL Code: "); builder.append(balCodeToString(code)); - if (verdict.equals(VERDICT_ALLOWED)) { - Slog.i(TAG, builder.toString()); - } else { - Slog.d(TAG, builder.toString()); - } + Slog.i(TAG, builder.toString()); } return code; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4fa6e2990faf..e7893da60847 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1622,7 +1622,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return; } + final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting() + ? new Transition.ReadyCondition("displayConfig", this) : null; + if (displayConfig != null) { + mTransitionController.waitFor(displayConfig); + } else if (mTransitionController.isShellTransitionsEnabled()) { + Slog.e(TAG, "Display reconfigured outside of a transition: " + this); + } final boolean configUpdated = updateDisplayOverrideConfigurationLocked(); + if (displayConfig != null) { + displayConfig.meet(); + } if (configUpdated) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 17bfeb453304..d69c5ef5ee91 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1034,20 +1034,15 @@ public class DisplayPolicy { } break; case TYPE_NAVIGATION_BAR: - mContext.enforcePermission( - android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, - "DisplayPolicy"); + mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE, + callingPid, callingUid, "DisplayPolicy"); if (mNavigationBar != null && mNavigationBar.isAlive()) { return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; } break; case TYPE_NAVIGATION_BAR_PANEL: - // Check for permission if the caller is not the recents component. - if (!mService.mAtmService.isCallerRecents(callingUid)) { - mContext.enforcePermission( - android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, - "DisplayPolicy"); - } + mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE, + callingPid, callingUid, "DisplayPolicy"); break; case TYPE_STATUS_BAR_ADDITIONAL: case TYPE_STATUS_BAR_SUB_PANEL: diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 1a319ad61116..fa2c94a1ecf2 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -87,6 +87,7 @@ class KeyguardController { private RootWindowContainer mRootWindowContainer; private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer; private boolean mWaitingForWakeTransition; + private Transition.ReadyCondition mWaitAodHide = null; KeyguardController(ActivityTaskManagerService service, ActivityTaskSupervisor taskSupervisor) { @@ -565,8 +566,14 @@ class KeyguardController { // Defer transition until AOD dismissed. void updateDeferTransitionForAod(boolean waiting) { - if (waiting == mWaitingForWakeTransition) { - return; + if (mService.getTransitionController().useFullReadyTracking()) { + if (waiting == (mWaitAodHide != null)) { + return; + } + } else { + if (waiting == mWaitingForWakeTransition) { + return; + } } if (!mService.getTransitionController().isCollecting()) { return; @@ -575,12 +582,17 @@ class KeyguardController { if (waiting && isAodShowing(DEFAULT_DISPLAY)) { mWaitingForWakeTransition = true; mWindowManager.mAtmService.getTransitionController().deferTransitionReady(); + mWaitAodHide = new Transition.ReadyCondition("AOD hidden"); + mWindowManager.mAtmService.getTransitionController().waitFor(mWaitAodHide); mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS); } else if (!waiting) { // dismiss the deferring if the AOD state change or cancel awake. mWaitingForWakeTransition = false; mWindowManager.mAtmService.getTransitionController().continueTransitionReady(); mWindowManager.mH.removeCallbacks(mResetWaitTransition); + final Transition.ReadyCondition waitAodHide = mWaitAodHide; + mWaitAodHide = null; + waitAodHide.meet(); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2281395f8cb5..c81105a11cee 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2051,6 +2051,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } transitionController.deferTransitionReady(); + Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip"); + transitionController.waitFor(pipChangesApplied); mService.deferWindowLayout(); try { // This will change the root pinned task's windowing mode to its original mode, ensuring @@ -2235,6 +2237,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } finally { transitionController.continueTransitionReady(); + pipChangesApplied.meet(); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6c31e3e69350..d2f6d1698f1a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4715,7 +4715,7 @@ class Task extends TaskFragment { } if (isAttached()) { setWindowingMode(WINDOWING_MODE_UNDEFINED); - moveTaskToBackInner(this); + moveTaskToBackInner(this, null /* transition */); } if (top.isAttached()) { top.setWindowingMode(WINDOWING_MODE_UNDEFINED); @@ -5718,24 +5718,27 @@ class Task extends TaskFragment { mTransitionController.requestStartTransition(transition, tr, null /* remoteTransition */, null /* displayChange */); mTransitionController.collect(tr); - moveTaskToBackInner(tr); + moveTaskToBackInner(tr, transition); }); } else { // Skip the transition for pinned task. if (!inPinnedWindowingMode()) { mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK); } - moveTaskToBackInner(tr); + moveTaskToBackInner(tr, null /* transition */); } return true; } - private boolean moveTaskToBackInner(@NonNull Task task) { - if (mTransitionController.isShellTransitionsEnabled()) { + private boolean moveTaskToBackInner(@NonNull Task task, @Nullable Transition transition) { + final Transition.ReadyCondition movedToBack = + new Transition.ReadyCondition("moved-to-back", task); + if (transition != null) { // Preventing from update surface position for WindowState if configuration changed, // because the position is depends on WindowFrame, so update the position before // relayout will only update it to "old" position. mAtmService.deferWindowLayout(); + transition.mReadyTracker.add(movedToBack); } try { moveToBack("moveTaskToBackInner", task); @@ -5752,6 +5755,9 @@ class Task extends TaskFragment { if (mTransitionController.isShellTransitionsEnabled()) { mAtmService.continueWindowLayout(); } + if (transition != null) { + movedToBack.meet(); + } } ActivityRecord topActivity = getDisplayArea().topRunningActivity(); Task topRootTask = topActivity == null ? null : topActivity.getRootTask(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7d65c61193b5..f6b4972de250 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -236,7 +236,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private IRemoteCallback mClientAnimationFinishCallback = null; private @TransitionState int mState = STATE_PENDING; - private final ReadyTracker mReadyTracker = new ReadyTracker(); + private final ReadyTrackerOld mReadyTrackerOld = new ReadyTrackerOld(); + final ReadyTracker mReadyTracker = new ReadyTracker(this); private int mRecentsDisplayId = INVALID_DISPLAY; @@ -656,7 +657,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { updateTransientFlags(info); mChanges.put(curr, info); if (isReadyGroup(curr)) { - mReadyTracker.addGroup(curr); + mReadyTrackerOld.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + " Transition %d with root=%s", mSyncId, curr); } @@ -887,13 +888,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ void setReady(WindowContainer wc, boolean ready) { if (!isCollecting() || mSyncId < 0) return; - mReadyTracker.setReadyFrom(wc, ready); + mReadyTrackerOld.setReadyFrom(wc, ready); applyReady(); } private void applyReady() { if (mState < STATE_STARTED) return; - final boolean ready = mReadyTracker.allReady(); + final boolean ready; + if (mController.useFullReadyTracking()) { + ready = mReadyTracker.isReady(); + } else { + ready = mReadyTrackerOld.allReady(); + } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Set transition ready=%b %d", ready, mSyncId); boolean changed = mSyncEngine.setReady(mSyncId, ready); @@ -909,22 +915,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { /** * Sets all possible ready groups to ready. - * @see ReadyTracker#setAllReady. + * @see ReadyTrackerOld#setAllReady */ void setAllReady() { if (!isCollecting() || mSyncId < 0) return; - mReadyTracker.setAllReady(); + mReadyTrackerOld.setAllReady(); applyReady(); } @VisibleForTesting boolean allReady() { - return mReadyTracker.allReady(); + return mReadyTrackerOld.allReady(); } /** This transition has all of its expected participants. */ boolean isPopulated() { - return mState >= STATE_STARTED && mReadyTracker.allReady(); + return mState >= STATE_STARTED && mReadyTrackerOld.allReady(); } /** @@ -1438,6 +1444,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d", mSyncId); mForcePlaying = true; + // backwards since conditions are removed. + for (int i = mReadyTracker.mConditions.size() - 1; i >= 0; --i) { + mReadyTracker.mConditions.get(i).meetAlternate("play-now"); + } + final ReadyCondition forcePlay = new ReadyCondition("force-play-now"); + mReadyTracker.add(forcePlay); + forcePlay.meet(); setAllReady(); if (mState == STATE_COLLECTING) { start(); @@ -1483,6 +1496,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return; } + if (mController.useFullReadyTracking()) { + if (mReadyTracker.mMet.isEmpty()) { + Slog.e(TAG, "#" + mSyncId + ": No conditions provided"); + } else { + for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) { + Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: " + + mReadyTracker.mConditions.get(i)); + } + } + for (int i = 0; i < mReadyTracker.mMet.size(); ++i) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s", + mSyncId, mReadyTracker.mMet.get(i)); + } + } + // Commit the visibility of visible activities before calculateTransitionInfo(), so the // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise // ActivityRecord#canShowWindows() may reject to show its window. The visibility also @@ -2797,7 +2825,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // if an activity is pausing, it will call setReady(ar, false) and wait for the next // resumed activity. Then do not set to ready because the transition only contains // partial participants. Otherwise the transition may only handle HIDE and miss OPEN. - if (!mReadyTracker.mUsed) { + if (!mReadyTrackerOld.mUsed) { setReady(dc, true); } } @@ -3059,19 +3087,160 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * {@link #continueTransitionReady} */ void deferTransitionReady() { - ++mReadyTracker.mDeferReadyDepth; + ++mReadyTrackerOld.mDeferReadyDepth; // Make sure it wait until #continueTransitionReady() is called. mSyncEngine.setReady(mSyncId, false); } /** This undoes one call to {@link #deferTransitionReady}. */ void continueTransitionReady() { - --mReadyTracker.mDeferReadyDepth; + --mReadyTrackerOld.mDeferReadyDepth; // Apply ready in case it is waiting for the previous defer call. applyReady(); } /** + * Represents a condition that must be met before an associated transition can be considered + * ready. + * + * Expected usage is that a ReadyCondition is created and then attached to a transition's + * ReadyTracker via {@link ReadyTracker#add}. After that, it is expected to monitor the state + * of the system and when the condition it represents is met, it will call + * {@link ReadyTracker#meet}. + * + * This base class is a simple explicit, named condition. A caller will create/attach the + * condition and then explicitly call {@link #meet} on it (which internally calls + * {@link ReadyTracker#meet}. + * + * Example: + * <pre> + * ReadyCondition myCondition = new ReadyCondition("my condition"); + * transitionController.waitFor(myCondition); + * ... Some operations ... + * myCondition.meet(); + * </pre> + */ + static class ReadyCondition { + final String mName; + + /** Just used for debugging */ + final Object mDebugTarget; + ReadyTracker mTracker; + boolean mMet = false; + + /** If set (non-null), then this is met by another reason besides state (eg. timeout). */ + String mAlternate = null; + + ReadyCondition(@NonNull String name) { + mName = name; + mDebugTarget = null; + } + + ReadyCondition(@NonNull String name, @Nullable Object debugTarget) { + mName = name; + mDebugTarget = debugTarget; + } + + protected String getDebugRep() { + if (mDebugTarget != null) { + return mName + ":" + mDebugTarget; + } + return mName; + } + + @Override + public String toString() { + return "{" + getDebugRep() + (mAlternate != null ? " (" + mAlternate + ")" : "") + "}"; + } + + /** + * Instructs this condition to start tracking system state to detect when this is met. + * Don't call this directly; it is called when this object is attached to a transition's + * ready-tracker. + */ + void startTracking() { + } + + /** + * Immediately consider this condition met by an alternative reason (one which doesn't + * match the normal intent of this condition -- eg. a timeout). + */ + void meetAlternate(@NonNull String reason) { + if (mMet) return; + mAlternate = reason; + meet(); + } + + /** Immediately consider this condition met. */ + void meet() { + if (mMet) return; + if (mTracker == null) { + throw new IllegalStateException("Can't meet a condition before it is waited on"); + } + mTracker.meet(this); + } + } + + static class ReadyTracker { + /** + * Used as a place-holder in situations where the transition system isn't active (such as + * early-boot, mid shell crash/recovery, or when using legacy). + */ + static final ReadyTracker NULL_TRACKER = new ReadyTracker(null); + + private final Transition mTransition; + + /** List of conditions that are still being waited on. */ + final ArrayList<ReadyCondition> mConditions = new ArrayList<>(); + + /** List of already-met conditions. Fully-qualified for debugging. */ + final ArrayList<ReadyCondition> mMet = new ArrayList<>(); + + ReadyTracker(Transition transition) { + mTransition = transition; + } + + void add(@NonNull ReadyCondition condition) { + if (mTransition == null || !mTransition.mController.useFullReadyTracking()) { + condition.mTracker = NULL_TRACKER; + return; + } + mConditions.add(condition); + condition.mTracker = this; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d", + condition, mTransition.mSyncId); + condition.startTracking(); + } + + void meet(@NonNull ReadyCondition condition) { + if (mTransition == null || !mTransition.mController.useFullReadyTracking()) return; + if (mTransition.mState >= STATE_PLAYING) { + Slog.w(TAG, "#%d: Condition met too late, already in state=" + mTransition.mState + + ": " + condition); + return; + } + if (!mConditions.remove(condition)) { + if (mMet.contains(condition)) { + throw new IllegalStateException("Can't meet the same condition more than once: " + + condition + " #" + mTransition.mSyncId); + } else { + throw new IllegalArgumentException("Can't meet a condition that isn't being " + + "waited on: " + condition + " in #" + mTransition.mSyncId); + } + } + condition.mMet = true; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d" + + " left)", condition, mTransition.mSyncId, mConditions.size()); + mMet.add(condition); + mTransition.applyReady(); + } + + boolean isReady() { + return mConditions.isEmpty() && !mMet.isEmpty(); + } + } + + /** * The transition sync mechanism has 2 parts: * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app * launch or stop or get a new configuration?). @@ -3084,7 +3253,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * of readiness across the multiple groups. Currently, we assume that each display is a group * since that is how it has been until now. */ - private static class ReadyTracker { + private static class ReadyTrackerOld { private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 8ac21e41f7f4..28c24c82e830 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -62,6 +62,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.server.FgThread; +import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.function.Consumer; @@ -131,6 +132,7 @@ class TransitionController { TransitionTracer mTransitionTracer; private SystemPerformanceHinter mSystemPerformanceHinter; + private boolean mFullReadyTracking = false; private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = new ArrayList<>(); @@ -281,6 +283,7 @@ class TransitionController { registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); setSyncEngine(wms.mSyncEngine); setSystemPerformanceHinter(wms.mSystemPerformanceHinter); + mFullReadyTracking = Flags.transitReadyTracking(); } @VisibleForTesting @@ -397,6 +400,14 @@ class TransitionController { return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION; } + boolean useFullReadyTracking() { + return mFullReadyTracking; + } + + void setFullReadyTrackingForTest(boolean enabled) { + mFullReadyTracking = enabled; + } + /** * @return {@code true} if transition is actively collecting changes. This is {@code false} * once a transition is playing @@ -1475,6 +1486,19 @@ class TransitionController { applySync.accept(false /* deferred */); } + /** + * Instructs the collecting transition to wait until `condition` is met before it is + * considered ready. + */ + void waitFor(@NonNull Transition.ReadyCondition condition) { + if (mCollectingTransition == null) { + Slog.e(TAG, "No collecting transition available to wait for " + condition); + condition.mTracker = Transition.ReadyTracker.NULL_TRACKER; + return; + } + mCollectingTransition.mReadyTracker.add(condition); + } + interface OnStartCollect { void onCollectStarted(boolean deferred); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5ed6caffe1fb..eb60aaba510f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -305,9 +305,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // This is a direct call from shell, so the entire transition lifecycle is // contained in the provided transaction if provided. Thus, we can setReady // immediately after apply. + final Transition.ReadyCondition wctApplied = + new Transition.ReadyCondition("start WCT applied"); final boolean needsSetReady = t != null; final Transition nextTransition = new Transition(type, 0 /* flags */, mTransitionController, mService.mWindowManager.mSyncEngine); + nextTransition.mReadyTracker.add(wctApplied); nextTransition.calcParallelCollectType(wct); mTransitionController.startCollectOrQueue(nextTransition, (deferred) -> { @@ -315,6 +318,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub nextTransition.mLogger.mStartWCT = wct; applyTransaction(wct, -1 /* syncId */, nextTransition, caller, deferred); + wctApplied.meet(); if (needsSetReady) { // TODO(b/294925498): Remove this once we have accurate ready // tracking. @@ -329,6 +333,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub }); return nextTransition.getToken(); } + // Currently, application of wct can span multiple looper loops (ie. + // waitAsyncStart), so add a condition to ensure that it finishes applying. + final Transition.ReadyCondition wctApplied; + if (t != null) { + wctApplied = new Transition.ReadyCondition("start WCT applied"); + transition.mReadyTracker.add(wctApplied); + } else { + wctApplied = null; + } // The transition already started collecting before sending a request to shell, // so just start here. if (!transition.isCollecting() && !transition.isForcePlaying()) { @@ -336,6 +349,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub + " means Shell took too long to respond to a request. WM State may be" + " incorrect now, please file a bug"); applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller); + if (wctApplied != null) { + wctApplied.meet(); + } return transition.getToken(); } transition.mLogger.mStartWCT = wct; @@ -344,11 +360,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub synchronized (mService.mGlobalLock) { transition.start(); applyTransaction(wct, -1 /* syncId */, transition, caller); + if (wctApplied != null) { + wctApplied.meet(); + } } }); } else { transition.start(); applyTransaction(wct, -1 /* syncId */, transition, caller); + if (wctApplied != null) { + wctApplied.meet(); + } } // Since the transition is already provided, it means WMCore is determining the // "readiness lifecycle" outside the provided transaction, so don't set ready here. @@ -1159,9 +1181,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: { final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); - if (container == null || container.asDisplayArea() == null - || !container.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached display area: " + if (container == null || !container.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + + container); + break; + } + if (container.asTask() == null && container.asDisplayArea() == null) { + Slog.e(TAG, "Cannot set always-on-top on non-task or non-display area: " + container); break; } diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 41706f0e5c53..8dfa685bf6ff 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -23,8 +23,6 @@ java_library_static { "services.core", "app-compat-annotations", "service-permission.stubs.system_server", - ], - static_libs: [ "device_policy_aconfig_flags_lib", ], } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 924e2f8ec654..0d024d6a4c58 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2083,17 +2083,14 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - // Devices without WebView/JavaScript cannot support PAC proxies. - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) { - t.traceBegin("StartPacProxyService"); - try { - pacProxyService = new PacProxyService(context); - ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService); - } catch (Throwable e) { - reportWtf("starting PacProxyService", e); - } - t.traceEnd(); + t.traceBegin("StartPacProxyService"); + try { + pacProxyService = new PacProxyService(context); + ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService); + } catch (Throwable e) { + reportWtf("starting PacProxyService", e); } + t.traceEnd(); t.traceBegin("StartConnectivityService"); // This has to be called after NetworkManagementService, NetworkStatsService diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index bded9b40e591..809a0e80dd63 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -919,6 +919,7 @@ public class ConnectivityControllerTest { assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants)); mSetFlagsRule.disableFlags(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER); when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); when(mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota( any(), eq(NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS))) @@ -938,6 +939,12 @@ public class ConnectivityControllerTest { assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants)); when(mService.isBatteryCharging()).thenReturn(true); + assertFalse(controller.isSatisfied(latePrefetch, net, caps, mConstants)); + // Only relax restrictions when we at least know the estimated download bytes. + assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants)); + assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants)); + + when(mService.isBatteryNotLow()).thenReturn(true); assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants)); // Only relax restrictions when we at least know the estimated download bytes. assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants)); 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 7a6ac4ef8f26..e7f1d16e1dfd 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.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; @@ -100,6 +101,8 @@ public class PackageArchiverTest { @Mock private LauncherApps mLauncherApps; @Mock + private ActivityManager mActivityManager; + @Mock private PackageManager mPackageManager; @Mock private PackageInstallerService mInstallerService; @@ -159,6 +162,10 @@ public class PackageArchiverTest { when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); + + when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); + when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100); + doReturn(mComputer).when(mPackageManagerService).snapshotComputer(); when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn( Binder.getCallingUid()); @@ -172,7 +179,7 @@ public class PackageArchiverTest { mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService)); doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE), - any(LauncherActivityInfo.class), eq(mUserId), anyInt()); + any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt()); doReturn(mIcon).when(mArchiveManager).decodeIcon( any(ArchiveState.ArchiveActivityInfo.class)); } @@ -277,7 +284,7 @@ public class PackageArchiverTest { public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException { IOException e = new IOException("IO"); doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE), - any(LauncherActivityInfo.class), eq(mUserId), anyInt()); + any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt()); mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java index 3808f30515fb..bfaf4959e4aa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java @@ -29,6 +29,8 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,6 +40,7 @@ import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.MagnificationConfig; +import android.companion.virtual.IVirtualDeviceListener; import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceManager; import android.content.ComponentName; @@ -50,6 +53,7 @@ import android.os.RemoteException; 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.util.ArraySet; import android.view.KeyEvent; import android.view.MotionEvent; @@ -74,6 +78,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -94,6 +99,9 @@ public class ProxyManagerTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private Context mMockContext; @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock private AccessibilityWindowManager mMockA11yWindowManager; @@ -114,6 +122,8 @@ public class ProxyManagerTest { @Before public void setup() throws RemoteException { + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); + MockitoAnnotations.initMocks(this); final Resources resources = InstrumentationRegistry.getContext().getResources(); @@ -121,6 +131,8 @@ public class ProxyManagerTest { resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width); mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color); when(mMockContext.getResources()).thenReturn(resources); + when(mMockContext.getMainExecutor()) + .thenReturn(InstrumentationRegistry.getTargetContext().getMainExecutor()); when(mMockVirtualDeviceManagerInternal.getDeviceIdsForUid(anyInt())).thenReturn( new ArraySet(Set.of(DEVICE_ID))); @@ -416,6 +428,101 @@ public class ProxyManagerTest { assertThat(focusStrokeWidth).isEqualTo(mFocusStrokeWidthDefaultValue); } + @Test + public void testRegisterProxy_registersVirtualDeviceListener() throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + + verify(mMockIVirtualDeviceManager, times(1)).registerVirtualDeviceListener(any()); + } + + @Test + public void testRegisterMultipleProxies_registersOneVirtualDeviceListener() + throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + registerProxy(DISPLAY_2_ID); + + verify(mMockIVirtualDeviceManager, times(1)).registerVirtualDeviceListener(any()); + } + + @Test + public void testUnregisterProxy_unregistersVirtualDeviceListener() throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + + mProxyManager.unregisterProxy(DISPLAY_ID); + + verify(mMockIVirtualDeviceManager, times(1)).unregisterVirtualDeviceListener(any()); + } + + @Test + public void testUnregisterProxy_onlyUnregistersVirtualDeviceListenerOnLastProxyRemoval() + throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + registerProxy(DISPLAY_2_ID); + + mProxyManager.unregisterProxy(DISPLAY_ID); + verify(mMockIVirtualDeviceManager, never()).unregisterVirtualDeviceListener(any()); + + mProxyManager.unregisterProxy(DISPLAY_2_ID); + verify(mMockIVirtualDeviceManager, times(1)).unregisterVirtualDeviceListener(any()); + } + + @Test + public void testRegisteredProxy_virtualDeviceClosed_proxyClosed() + throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + + assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue(); + assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue(); + + ArgumentCaptor<IVirtualDeviceListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(IVirtualDeviceListener.class); + verify(mMockIVirtualDeviceManager, times(1)) + .registerVirtualDeviceListener(listenerArgumentCaptor.capture()); + + listenerArgumentCaptor.getValue().onVirtualDeviceClosed(DEVICE_ID); + + verify(mMockProxySystemSupport, timeout(5_000)).removeDeviceIdLocked(DEVICE_ID); + + assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isFalse(); + assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isFalse(); + } + + @Test + public void testRegisteredProxy_unrelatedVirtualDeviceClosed_proxyNotClosed() + throws RemoteException { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + + assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue(); + assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue(); + + ArgumentCaptor<IVirtualDeviceListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(IVirtualDeviceListener.class); + verify(mMockIVirtualDeviceManager, times(1)) + .registerVirtualDeviceListener(listenerArgumentCaptor.capture()); + + listenerArgumentCaptor.getValue().onVirtualDeviceClosed(DEVICE_ID + 1); + + assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue(); + assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isTrue(); + } + + @Test + public void testRegisterProxy_doesNotRegisterVirtualDeviceListener_flagDisabled() + throws RemoteException { + mSetFlagsRule.disableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS); + registerProxy(DISPLAY_ID); + mProxyManager.unregisterProxy(DISPLAY_ID); + + verify(mMockIVirtualDeviceManager, never()).registerVirtualDeviceListener(any()); + verify(mMockIVirtualDeviceManager, never()).unregisterVirtualDeviceListener(any()); + } + private void registerProxy(int displayId) { try { mProxyManager.registerProxy(mMockAccessibilityServiceClient, displayId, anyInt(), diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 24a628eb4331..d26d67107001 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -345,7 +345,7 @@ public class UserControllerTest { assertWithMessage("should not have received intents") .that(getActions(mInjector.mSentIntents)).isEmpty(); // TODO(b/140868593): should have received a USER_UNLOCK_MSG message as well, but it doesn't - // because StorageManager.isUserKeyUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to + // because StorageManager.isCeStorageUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to // properly fix it, we'd need to move this class to FrameworksMockingServicesTests so we can // mock static methods (but moving this class would involve changing the presubmit tests, // and the cascade effect goes on...). In fact, a better approach would to not assert the @@ -648,7 +648,7 @@ public class UserControllerTest { // checking. waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(0)) - .lockUserKey(anyInt()); + .lockCeStorage(anyInt()); addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, numerOfUserSwitches, true); @@ -663,7 +663,7 @@ public class UserControllerTest { mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(1)) - .lockUserKey(TEST_USER_ID); + .lockCeStorage(TEST_USER_ID); } /** @@ -757,7 +757,7 @@ public class UserControllerTest { mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND); verify(mInjector.mStorageManagerMock, never()) - .unlockUserKey(eq(TEST_USER_ID), anyInt(), any()); + .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any()); } @Test @@ -1035,7 +1035,7 @@ public class UserControllerTest { mUserController.finishUserStopped(ussUser, delayedLocking); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0)) - .lockUserKey(userId); + .lockCeStorage(userId); } private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId, 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 e3e708ec856d..0230d77e8e14 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -47,7 +47,6 @@ 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; @@ -65,7 +64,6 @@ 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; @@ -1753,45 +1751,6 @@ 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/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index fe2ac176949d..f5d50d173466 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -304,17 +304,17 @@ public abstract class BaseLockSettingsServiceTests { doAnswer(invocation -> { Object[] args = invocation.getArguments(); - mStorageManager.unlockUserKey(/* userId= */ (int) args[0], + mStorageManager.unlockCeStorage(/* userId= */ (int) args[0], /* secret= */ (byte[]) args[2]); return null; - }).when(sm).unlockUserKey(anyInt(), anyInt(), any()); + }).when(sm).unlockCeStorage(anyInt(), anyInt(), any()); doAnswer(invocation -> { Object[] args = invocation.getArguments(); - mStorageManager.setUserKeyProtection(/* userId= */ (int) args[0], + mStorageManager.setCeStorageProtection(/* userId= */ (int) args[0], /* secret= */ (byte[]) args[1]); return null; - }).when(sm).setUserKeyProtection(anyInt(), any()); + }).when(sm).setCeStorageProtection(anyInt(), any()); return sm; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java index 91f3fed01267..c08ad134d74a 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java @@ -24,7 +24,7 @@ public class FakeStorageManager { private final ArrayMap<Integer, byte[]> mUserSecrets = new ArrayMap<>(); - public void setUserKeyProtection(int userId, byte[] secret) { + public void setCeStorageProtection(int userId, byte[] secret) { assertThat(mUserSecrets).doesNotContainKey(userId); mUserSecrets.put(userId, secret); } @@ -35,7 +35,7 @@ public class FakeStorageManager { return secret; } - public void unlockUserKey(int userId, byte[] secret) { + public void unlockCeStorage(int userId, byte[] secret) { assertThat(mUserSecrets.get(userId)).isEqualTo(secret); } } diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 9fca513e50b9..d09aa89179b8 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -25,8 +25,6 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -46,6 +44,7 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; +import android.util.Log; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -58,7 +57,14 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; /** * Tests for {@link com.android.server.power.hint.HintManagerService}. @@ -67,8 +73,11 @@ import java.util.concurrent.CountDownLatch; * atest FrameworksServicesTests:HintManagerServiceTest */ public class HintManagerServiceTest { + private static final String TAG = "HintManagerServiceTest"; + private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L; private static final long DEFAULT_TARGET_DURATION = 16666666L; + private static final long CONCURRENCY_TEST_DURATION_SEC = 10; private static final int UID = Process.myUid(); private static final int TID = Process.myPid(); private static final int TGID = Process.getThreadGroupLeader(TID); @@ -103,6 +112,55 @@ public class HintManagerServiceTest { LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } + static class NativeWrapperFake extends NativeWrapper { + @Override + public void halInit() { + } + + @Override + public long halGetHintSessionPreferredRate() { + return 1; + } + + @Override + public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) { + return 1; + } + + @Override + public void halPauseHintSession(long halPtr) { + } + + @Override + public void halResumeHintSession(long halPtr) { + } + + @Override + public void halCloseHintSession(long halPtr) { + } + + @Override + public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) { + } + + @Override + public void halReportActualWorkDuration( + long halPtr, long[] actualDurationNanos, long[] timeStampNanos) { + } + + @Override + public void halSendHint(long halPtr, int hint) { + } + + @Override + public void halSetThreads(long halPtr, int[] tids) { + } + + @Override + public void halSetMode(long halPtr, int mode, boolean enabled) { + } + } + private HintManagerService createService() { mService = new HintManagerService(mContext, new Injector() { NativeWrapper createNativeWrapper() { @@ -112,6 +170,15 @@ public class HintManagerServiceTest { return mService; } + private HintManagerService createServiceWithFakeWrapper() { + mService = new HintManagerService(mContext, new Injector() { + NativeWrapper createNativeWrapper() { + return new NativeWrapperFake(); + } + }); + return mService; + } + @Test public void testInitializeService() { HintManagerService service = createService(); @@ -166,8 +233,7 @@ public class HintManagerServiceTest { latch.countDown(); }); latch.await(); - - assumeFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong()); // Set session to foreground and calling updateHintAllowed() would invoke resume(); @@ -181,7 +247,7 @@ public class HintManagerServiceTest { }); latch2.await(); - assumeTrue(a.updateHintAllowed()); + assertTrue(service.mUidObserver.isUidForeground(a.mUid)); verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong()); } @@ -254,7 +320,7 @@ public class HintManagerServiceTest { }); latch.await(); - assumeFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); } @@ -280,7 +346,7 @@ public class HintManagerServiceTest { service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); FgThread.getHandler().runWithScissors(() -> { }, 500); - assertFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt()); } @@ -303,7 +369,7 @@ public class HintManagerServiceTest { }); latch.await(); - assertFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); } @Test @@ -316,7 +382,7 @@ public class HintManagerServiceTest { service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); - assertTrue(a.updateHintAllowed()); + assertTrue(service.mUidObserver.isUidForeground(a.mUid)); } @Test @@ -342,7 +408,7 @@ public class HintManagerServiceTest { service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); FgThread.getHandler().runWithScissors(() -> { }, 500); - assertFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); a.setThreads(SESSION_TIDS_A); verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any()); } @@ -372,9 +438,159 @@ public class HintManagerServiceTest { service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); FgThread.getHandler().runWithScissors(() -> { }, 500); - assertFalse(a.updateHintAllowed()); + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); a.setMode(0, true); verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); } + // This test checks that concurrent operations from different threads on IHintService, + // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also + // check the output of threads' reportActualDuration performance to detect lock starvation + // but the result is not stable, so it's better checked manually. + @Test + public void testConcurrency() throws Exception { + HintManagerService service = createServiceWithFakeWrapper(); + // initialize session threads to run in parallel + final int sessionCount = 10; + // the signal that the main thread will send to session threads to check for run or exit + AtomicReference<Boolean> shouldRun = new AtomicReference<>(true); + // the signal for main test thread to wait for session threads and notifier thread to + // finish and exit + CountDownLatch latch = new CountDownLatch(sessionCount + 1); + // list of exceptions with one per session thread or notifier thread + List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1); + List<Thread> threads = new ArrayList<>(sessionCount + 1); + for (int i = 0; i < sessionCount; i++) { + final AtomicReference<Exception> exec = new AtomicReference<>(); + execs.add(exec); + int j = i; + Thread app = new Thread(() -> { + try { + while (shouldRun.get()) { + runAppHintSession(service, j, shouldRun); + } + } catch (Exception e) { + exec.set(e); + } finally { + latch.countDown(); + } + }); + threads.add(app); + } + + // initialize a UID state notifier thread to run in parallel + final AtomicReference<Exception> notifierExec = new AtomicReference<>(); + execs.add(notifierExec); + Thread notifier = new Thread(() -> { + try { + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + long sum = 0; + int count = 0; + while (shouldRun.get()) { + long start = System.nanoTime(); + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + long elapsed = System.nanoTime() - start; + sum += elapsed; + count++; + min = Math.min(min, elapsed); + max = Math.max(max, elapsed); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); + service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); + } + Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count); + service.mUidObserver.onUidGone(UID, true); + } catch (Exception e) { + notifierExec.set(e); + } finally { + latch.countDown(); + } + }); + threads.add(notifier); + + // start all the threads + for (Thread thread : threads) { + thread.start(); + } + // keep the test running for a few seconds + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC)); + // send signal to stop all threads + shouldRun.set(false); + // wait for all threads to exit + latch.await(); + // check if any thread throws exception + for (AtomicReference<Exception> exec : execs) { + if (exec.get() != null) { + throw exec.get(); + } + } + } + + private void runAppHintSession(HintManagerService service, int logId, + AtomicReference<Boolean> shouldRun) throws Exception { + IBinder token = new Binder(); + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + // we will start some threads and get their valid TIDs to update + int threadCount = 3; + // the list of TIDs + int[] tids = new int[threadCount]; + // atomic index for each thread to set its TID in the list + AtomicInteger k = new AtomicInteger(0); + // signal for the session main thread to wait for child threads to finish updating TIDs + CountDownLatch latch = new CountDownLatch(threadCount); + // signal for the session main thread to notify child threads to exit + CountDownLatch stopLatch = new CountDownLatch(1); + for (int j = 0; j < threadCount; j++) { + Thread thread = new Thread(() -> { + try { + tids[k.getAndIncrement()] = android.os.Process.myTid(); + latch.countDown(); + stopLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + thread.start(); + } + latch.await(); + a.setThreads(tids); + // we don't need the threads to exist after update + stopLatch.countDown(); + a.updateTargetWorkDuration(5); + // measure the time it takes in HintManagerService to run reportActualDuration + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + long sum = 0; + int count = 0; + List<Long> values = new ArrayList<>(); + long testStart = System.nanoTime(); + // run report actual for 4-second per cycle + while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos( + Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) { + long start = System.nanoTime(); + a.reportActualWorkDuration(new long[]{5}, new long[]{start}); + long elapsed = System.nanoTime() - start; + values.add(elapsed); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5)); + sum += elapsed; + count++; + min = Math.min(min, elapsed); + max = Math.max(max, elapsed); + } + Collections.sort(values); + if (!values.isEmpty()) { + Log.d(TAG, "app thread " + logId + " min " + min + " max " + max + + " avg " + sum / count + " count " + count + + " 80th " + values.get((int) (values.size() * 0.8)) + + " 90th " + values.get((int) (values.size() * 0.9)) + + " 95th " + values.get((int) (values.size() * 0.95))); + } else { + Log.w(TAG, "No reportActualWorkDuration executed"); + } + a.close(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 51b9c176a245..47f15b8df076 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT; import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; +import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -486,6 +487,29 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test + public void testRepostAll() throws Exception { + final int profileId = 11; + final int otherUserId = 2; + IntArray userIds = new IntArray(); + userIds.add(UserHandle.USER_SYSTEM); + userIds.add(profileId); + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); + NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.of(profileId)); + NotificationRecord r4 = getNotificationRecord("pkg", 4, "four", UserHandle.of(otherUserId)); + mSnoozeHelper.snooze(r, 1000); + mSnoozeHelper.snooze(r2, 1000); + mSnoozeHelper.snooze(r3, 1000); + mSnoozeHelper.snooze(r4, 1000); + + mSnoozeHelper.repostAll(userIds); + + verify(mCallback, times(3)).repost(anyInt(), any(), anyBoolean()); + // All notifications were reposted, except the one for otherUserId + assertThat(mSnoozeHelper.getSnoozed()).containsExactly(r4); + } + + @Test public void testGetSnoozedBy() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index c8546c613995..47730237f675 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -2475,6 +2475,46 @@ public class TransitionTests extends WindowTestsBase { verify(session).close(); } + @Test + public void testReadyTrackerBasics() { + final TransitionController controller = new TestTransitionController( + mock(ActivityTaskManagerService.class)); + controller.setFullReadyTrackingForTest(true); + Transition transit = createTestTransition(TRANSIT_OPEN, controller); + // Not ready if nothing has happened yet + assertFalse(transit.mReadyTracker.isReady()); + + Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1"); + transit.mReadyTracker.add(condition1); + assertFalse(transit.mReadyTracker.isReady()); + + Transition.ReadyCondition condition2 = new Transition.ReadyCondition("c2"); + transit.mReadyTracker.add(condition2); + assertFalse(transit.mReadyTracker.isReady()); + + condition2.meet(); + assertFalse(transit.mReadyTracker.isReady()); + + condition1.meet(); + assertTrue(transit.mReadyTracker.isReady()); + } + + @Test + public void testReadyTrackerAlternate() { + final TransitionController controller = new TestTransitionController( + mock(ActivityTaskManagerService.class)); + controller.setFullReadyTrackingForTest(true); + Transition transit = createTestTransition(TRANSIT_OPEN, controller); + + Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1"); + transit.mReadyTracker.add(condition1); + assertFalse(transit.mReadyTracker.isReady()); + + condition1.meetAlternate("reason1"); + assertTrue(transit.mReadyTracker.isReady()); + assertEquals("reason1", condition1.mAlternate); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { 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 0b77fd828745..cd3fef6b6fdb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1646,6 +1646,28 @@ public class WindowOrganizerTests extends WindowTestsBase { verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities(); } + @Test + public void testSetAlwaysOnTop() { + final Task rootTask = new TaskBuilder(mSupervisor) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + testSetAlwaysOnTop(rootTask); + + final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea(); + displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); + testSetAlwaysOnTop(displayArea); + } + + private void testSetAlwaysOnTop(WindowContainer wc) { + final WindowContainerTransaction t = new WindowContainerTransaction(); + t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + assertTrue(wc.isAlwaysOnTop()); + + t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + assertFalse(wc.isAlwaysOnTop()); + } + private ActivityRecord createActivityRecordAndDispatchPendingEvents(Task task) { final ActivityRecord record = createActivityRecord(task); // Flush EVENT_APPEARED. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 93b5a40a5fc0..f6c6a64ba764 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -19,6 +19,7 @@ package com.android.server.voiceinteraction; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.LOG_COMPAT_CHANGE; import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; import static android.Manifest.permission.RECORD_AUDIO; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; @@ -29,6 +30,8 @@ import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATU import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_COPY_AUDIO_DATA_FAILURE; +import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED; +import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS; @@ -75,6 +78,8 @@ import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordDetector; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; +import android.service.voice.HotwordTrainingDataLimitEnforcer; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.VisualQueryDetectionServiceFailure; @@ -127,6 +132,9 @@ abstract class DetectorSession { private static final String HOTWORD_DETECTION_OP_MESSAGE = "Providing hotword detection result to VoiceInteractionService"; + private static final String HOTWORD_TRAINING_DATA_OP_MESSAGE = + "Providing hotword training data to VoiceInteractionService"; + // The error codes are used for onHotwordDetectionServiceFailure callback. // Define these due to lines longer than 100 characters. static final int ONDETECTED_GOT_SECURITY_EXCEPTION = @@ -512,6 +520,25 @@ abstract class DetectorSession { } @Override + public void onTrainingData(HotwordTrainingData data) + throws RemoteException { + sendTrainingData(new TrainingDataEgressCallback() { + @Override + public void onHotwordDetectionServiceFailure( + HotwordDetectionServiceFailure failure) + throws RemoteException { + callback.onHotwordDetectionServiceFailure(failure); + } + + @Override + public void onTrainingData(HotwordTrainingData data) + throws RemoteException { + callback.onTrainingData(data); + } + }, data); + } + + @Override public void onDetected(HotwordDetectedResult triggerResult) throws RemoteException { synchronized (mLock) { @@ -593,6 +620,82 @@ abstract class DetectorSession { mVoiceInteractionServiceUid); } + /** Used to send training data. + * + * @hide + */ + interface TrainingDataEgressCallback { + /** Called to send training data */ + void onTrainingData(HotwordTrainingData trainingData) throws RemoteException; + + /** Called to inform failure to send training data. */ + void onHotwordDetectionServiceFailure(HotwordDetectionServiceFailure failure) throws + RemoteException; + + } + + /** Default implementation to send training data from {@link HotwordDetectionService} + * to {@link HotwordDetector}. + * + * <p> Verifies RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA permission has been + * granted and training data egress is within daily limit. + * + * @param callback used to send training data or inform of failures to send training data. + * @param data training data to egress. + * + * @hide + */ + void sendTrainingData( + TrainingDataEgressCallback callback, HotwordTrainingData data) throws RemoteException { + Slog.d(TAG, "onTrainingData()"); + + // Check training data permission is granted. + try { + enforcePermissionForTrainingDataDelivery(); + } catch (SecurityException e) { + Slog.w(TAG, "Ignoring training data due to a SecurityException", e); + try { + callback.onHotwordDetectionServiceFailure( + new HotwordDetectionServiceFailure( + ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION, + "Security exception occurred" + + "in #onTrainingData method.")); + } catch (RemoteException e1) { + notifyOnDetectorRemoteException(); + throw e1; + } + return; + } + + // Check whether within daily egress limit. + boolean withinEgressLimit = HotwordTrainingDataLimitEnforcer.getInstance(mContext) + .incrementEgressCount(); + if (!withinEgressLimit) { + Slog.d(TAG, "Ignoring training data as exceeded egress limit."); + try { + callback.onHotwordDetectionServiceFailure( + new HotwordDetectionServiceFailure( + ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED, + "Training data egress limit exceeded.")); + } catch (RemoteException e) { + notifyOnDetectorRemoteException(); + throw e; + } + return; + } + + try { + Slog.i(TAG, "Egressing training data from hotword trusted process."); + if (mDebugHotwordLogging) { + Slog.d(TAG, "Egressing hotword training data " + data); + } + callback.onTrainingData(data); + } catch (RemoteException e) { + notifyOnDetectorRemoteException(); + throw e; + } + } + void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { synchronized (mLock) { if (mInitialized || mDestroyed) { @@ -781,6 +884,27 @@ abstract class DetectorSession { } /** + * Enforces permission for training data delivery. + * + * <p> Throws a {@link SecurityException} if training data egress permission is not granted. + */ + void enforcePermissionForTrainingDataDelivery() { + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, + RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + HOTWORD_TRAINING_DATA_OP_MESSAGE); + + mAppOpsManager.noteOpNoThrow( + AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag, + HOTWORD_TRAINING_DATA_OP_MESSAGE); + } + }); + } + + /** * Throws a {@link SecurityException} if the given identity has no permission to receive data. * * @param context A {@link Context}, used for permission checks. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java index 9a4fbdc4516a..6418f3e83114 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java @@ -42,6 +42,7 @@ import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordDetector; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; import android.service.voice.IDspHotwordDetectionCallback; import android.util.Slog; @@ -229,6 +230,23 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { } } } + + @Override + public void onTrainingData(HotwordTrainingData data) throws RemoteException { + sendTrainingData(new TrainingDataEgressCallback() { + @Override + public void onHotwordDetectionServiceFailure( + HotwordDetectionServiceFailure failure) throws RemoteException { + externalCallback.onHotwordDetectionServiceFailure(failure); + } + + @Override + public void onTrainingData(HotwordTrainingData data) + throws RemoteException { + externalCallback.onTrainingData(data); + } + }, data); + } }; mValidatingDspTrigger = true; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 0a70a5f9b947..b098e828fb5f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -214,7 +214,6 @@ final class HotwordDetectionConnection { new ServiceConnectionFactory(visualQueryDetectionServiceIntent, bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY); - mLastRestartInstant = Instant.now(); if (mReStartPeriodSeconds <= 0) { @@ -918,7 +917,8 @@ final class HotwordDetectionConnection { session = new SoftwareTrustedHotwordDetectorSession( mRemoteHotwordDetectionService, mLock, mContext, token, callback, mVoiceInteractionServiceUid, mVoiceInteractorIdentity, - mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener); + mScheduledExecutorService, mDebugHotwordLogging, + mRemoteExceptionListener); } mHotwordRecognitionCallback = callback; mDetectorSessions.put(detectorType, session); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java index f06c99729a19..2e23eff7a179 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java @@ -40,6 +40,7 @@ import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordDetector; import android.service.voice.HotwordRejectedResult; +import android.service.voice.HotwordTrainingData; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.ISandboxedDetectionService; @@ -195,6 +196,21 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { mVoiceInteractionServiceUid); // onRejected isn't allowed here, and we are not expecting it. } + + public void onTrainingData(HotwordTrainingData data) throws RemoteException { + sendTrainingData(new TrainingDataEgressCallback() { + @Override + public void onHotwordDetectionServiceFailure( + HotwordDetectionServiceFailure failure) throws RemoteException { + mSoftwareCallback.onHotwordDetectionServiceFailure(failure); + } + + @Override + public void onTrainingData(HotwordTrainingData data) throws RemoteException { + mSoftwareCallback.onTrainingData(data); + } + }, data); + } }; mRemoteDetectionService.run( diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1c689d0d5ce3..a584fc9b2216 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1541,7 +1541,31 @@ public class VoiceInteractionManagerService extends SystemService { } } } - //----------------- Model management APIs --------------------------------// + + @Override + @android.annotation.EnforcePermission( + android.Manifest.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT) + public void resetHotwordTrainingDataEgressCountForTest() { + super.resetHotwordTrainingDataEgressCountForTest_enforcePermission(); + synchronized (this) { + enforceIsCurrentVoiceInteractionService(); + + if (mImpl == null) { + Slog.w(TAG, "resetHotwordTrainingDataEgressCountForTest without running" + + " voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.resetHotwordTrainingDataEgressCountForTest(); + } finally { + Binder.restoreCallingIdentity(caller); + } + + } + } + + //----------------- Model management APIs --------------------------------// @Override public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 6ba77da1d972..3c4b58fa2b69 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -60,6 +60,7 @@ import android.os.SharedMemory; import android.os.SystemProperties; import android.os.UserHandle; import android.service.voice.HotwordDetector; +import android.service.voice.HotwordTrainingDataLimitEnforcer; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; import android.service.voice.IVoiceInteractionService; @@ -72,6 +73,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVisualQueryDetectionAttentionListener; import com.android.internal.app.IVoiceActionCheckCallback; @@ -991,6 +993,12 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } + @VisibleForTesting + void resetHotwordTrainingDataEgressCountForTest() { + HotwordTrainingDataLimitEnforcer.getInstance(mContext.getApplicationContext()) + .resetTrainingDataEgressCount(); + } + void startLocked() { Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); intent.setComponent(mComponent); diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp deleted file mode 100644 index e17209088750..000000000000 --- a/startop/view_compiler/Android.bp +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -cc_defaults { - name: "viewcompiler_defaults", - header_libs: [ - "libbase_headers", - ], - shared_libs: [ - "libbase", - "slicer", - ], - static_libs: [ - "libcutils", - "libtinyxml2", - "liblog", - "libutils", - "libziparchive", - "libz", - ], - cpp_std: "gnu++2b", - target: { - android: { - shared_libs: [ - "libandroidfw", - ], - }, - host: { - static_libs: [ - "libandroidfw", - ], - }, - }, -} - -cc_library_static { - name: "libviewcompiler", - defaults: ["viewcompiler_defaults"], - srcs: [ - "apk_layout_compiler.cc", - "dex_builder.cc", - "dex_layout_compiler.cc", - "java_lang_builder.cc", - "tinyxml_layout_parser.cc", - "util.cc", - "layout_validation.cc", - ], - host_supported: true, -} - -cc_binary { - name: "viewcompiler", - defaults: ["viewcompiler_defaults"], - srcs: [ - "main.cc", - ], - static_libs: [ - "libgflags", - "libviewcompiler", - ], - host_supported: true, -} - -cc_test_host { - name: "view-compiler-tests", - defaults: ["viewcompiler_defaults"], - srcs: [ - "layout_validation_test.cc", - "util_test.cc", - ], - static_libs: [ - "libviewcompiler", - ], -} - -cc_binary_host { - name: "dex_testcase_generator", - defaults: ["viewcompiler_defaults"], - srcs: ["dex_testcase_generator.cc"], - static_libs: [ - "libviewcompiler", - ], -} - -genrule { - name: "generate_dex_testcases", - tools: [":dex_testcase_generator"], - cmd: "$(location :dex_testcase_generator) $(genDir)", - out: [ - "simple.dex", - "trivial.dex", - ], -} diff --git a/startop/view_compiler/OWNERS b/startop/view_compiler/OWNERS deleted file mode 100644 index e5aead9ddac8..000000000000 --- a/startop/view_compiler/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -eholk@google.com -mathieuc@google.com diff --git a/startop/view_compiler/README.md b/startop/view_compiler/README.md deleted file mode 100644 index f8da02b53907..000000000000 --- a/startop/view_compiler/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# View Compiler - -This directory contains an experimental compiler for layout files. - -It will take a layout XML file and produce a CompiledLayout.java file with a -specialized layout inflation function. - -To use it, let's assume you had a layout in `my_layout.xml` and your app was in -the Java language package `com.example.myapp`. Run the following command: - - viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java - -This will produce a `CompiledView.java`, which can then be compiled into your -Android app. Then to use it, in places where you would have inflated -`R.layouts.my_layout`, instead call `CompiledView.inflate`. - -Precompiling views like this generally improves the time needed to inflate them. - -This tool is still in its early stages and has a number of limitations. -* Currently only one layout can be compiled at a time. -* `merge` and `include` nodes are not supported. -* View compilation is a manual process that requires code changes in the - application. -* This only works for apps that do not use a custom layout inflater. -* Other limitations yet to be discovered. - -## DexBuilder Tests - -The DexBuilder has several low-level end to end tests to verify generated DEX -code validates, runs, and has the correct behavior. There are, unfortunately, a -number of pieces that must be added to generate new tests. Here are the -components: - -* `dex_testcase_generator` - Written in C++ using `DexBuilder`. This runs as a - build step produce the DEX files that will be tested on device. See the - `genrule` named `generate_dex_testcases` in `Android.bp`. These files are then - copied over to the device by TradeFed when running tests. -* `DexBuilderTest` - This is a Java Language test harness that loads the - generated DEX files and exercises methods in the file. - -To add a new DEX file test, follow these steps: -1. Modify `dex_testcase_generator` to produce the DEX file. -2. Add the filename to the `out` list of the `generate_dex_testcases` rule in - `Android.bp`. -3. Add a new `push` option to `AndroidTest.xml` to copy the DEX file to the - device. -4. Modify `DexBuilderTest.java` to load and exercise the new test. - -In each case, you should be able to cargo-cult the existing test cases. - -In general, you can probably get by without adding a new generated DEX file, and -instead add more methods to the files that are already generated. In this case, -you can skip all of steps 2 and 3 above, and simplify steps 1 and 4. diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc deleted file mode 100644 index 5f5652c2acac..000000000000 --- a/startop/view_compiler/apk_layout_compiler.cc +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2018 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 "apk_layout_compiler.h" -#include "dex_layout_compiler.h" -#include "java_lang_builder.h" -#include "layout_validation.h" -#include "util.h" - -#include "androidfw/ApkAssets.h" -#include "androidfw/AssetManager2.h" -#include "androidfw/ResourceTypes.h" - -#include <iostream> -#include <locale> - -#include "android-base/stringprintf.h" - -namespace startop { - -using android::ResXMLParser; -using android::base::StringPrintf; - -class ResXmlVisitorAdapter { - public: - ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {} - - template <typename Visitor> - void Accept(Visitor* visitor) { - size_t depth{0}; - do { - switch (parser_->next()) { - case ResXMLParser::START_DOCUMENT: - depth++; - visitor->VisitStartDocument(); - break; - case ResXMLParser::END_DOCUMENT: - depth--; - visitor->VisitEndDocument(); - break; - case ResXMLParser::START_TAG: { - depth++; - size_t name_length = 0; - const char16_t* name = parser_->getElementName(&name_length); - visitor->VisitStartTag(std::u16string{name, name_length}); - break; - } - case ResXMLParser::END_TAG: - depth--; - visitor->VisitEndTag(); - break; - default:; - } - } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE); - } - - private: - ResXMLParser* parser_; -}; - -bool CanCompileLayout(ResXMLParser* parser) { - ResXmlVisitorAdapter adapter{parser}; - LayoutValidationVisitor visitor; - adapter.Accept(&visitor); - - return visitor.can_compile(); -} - -namespace { -void CompileApkAssetsLayouts(const android::ApkAssetsPtr& assets, CompilationTarget target, - std::ostream& target_out) { - android::AssetManager2 resources; - resources.SetApkAssets({assets}); - - std::string package_name; - - // TODO: handle multiple packages better - bool first = true; - for (const auto& package : assets->GetLoadedArsc()->GetPackages()) { - CHECK(first); - package_name = package->GetPackageName(); - first = false; - } - - dex::DexBuilder dex_file; - dex::ClassBuilder compiled_view{ - dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))}; - std::vector<dex::MethodBuilder> methods; - - assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) { - if (s == "layout") { - auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data()); - assets->GetAssetsProvider() - ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) { - auto layout_path = StringPrintf("%s%.*s", path.c_str(), - (int)layout_file.size(), layout_file.data()); - android::ApkAssetsCookie cookie = android::kInvalidCookie; - auto asset = resources.OpenNonAsset(layout_path, - android::Asset::ACCESS_RANDOM, &cookie); - CHECK(asset); - CHECK(android::kInvalidCookie != cookie); - const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie); - CHECK(nullptr != dynamic_ref_table); - android::ResXMLTree xml_tree{dynamic_ref_table}; - xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(), - /*copy_data=*/true); - android::ResXMLParser parser{xml_tree}; - parser.restart(); - if (CanCompileLayout(&parser)) { - parser.restart(); - const std::string layout_name = - startop::util::FindLayoutNameFromFilename(layout_path); - ResXmlVisitorAdapter adapter{&parser}; - switch (target) { - case CompilationTarget::kDex: { - methods.push_back(compiled_view.CreateMethod( - layout_name, - dex::Prototype{dex::TypeDescriptor::FromClassname( - "android.view.View"), - dex::TypeDescriptor::FromClassname( - "android.content.Context"), - dex::TypeDescriptor::Int()})); - DexViewBuilder builder(&methods.back()); - builder.Start(); - LayoutCompilerVisitor visitor{&builder}; - adapter.Accept(&visitor); - builder.Finish(); - methods.back().Encode(); - break; - } - case CompilationTarget::kJavaLanguage: { - JavaLangViewBuilder builder{package_name, layout_name, - target_out}; - builder.Start(); - LayoutCompilerVisitor visitor{&builder}; - adapter.Accept(&visitor); - builder.Finish(); - break; - } - } - } - }); - } - }); - - if (target == CompilationTarget::kDex) { - slicer::MemView image{dex_file.CreateImage()}; - target_out.write(image.ptr<const char>(), image.size()); - } -} -} // namespace - -void CompileApkLayouts(const std::string& filename, CompilationTarget target, - std::ostream& target_out) { - auto assets = android::ApkAssets::Load(filename); - CompileApkAssetsLayouts(assets, target, target_out); -} - -void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target, - std::ostream& target_out) { - constexpr const char* friendly_name{"viewcompiler assets"}; - auto assets = android::ApkAssets::LoadFromFd(std::move(fd), friendly_name); - CompileApkAssetsLayouts(assets, target, target_out); -} - -} // namespace startop diff --git a/startop/view_compiler/apk_layout_compiler.h b/startop/view_compiler/apk_layout_compiler.h deleted file mode 100644 index 03bd545d9121..000000000000 --- a/startop/view_compiler/apk_layout_compiler.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#ifndef APK_LAYOUT_COMPILER_H_ -#define APK_LAYOUT_COMPILER_H_ - -#include <string> - -#include "android-base/unique_fd.h" - -namespace startop { - -enum class CompilationTarget { kJavaLanguage, kDex }; - -void CompileApkLayouts(const std::string& filename, CompilationTarget target, - std::ostream& target_out); -void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target, - std::ostream& target_out); - -} // namespace startop - -#endif // APK_LAYOUT_COMPILER_H_
\ No newline at end of file diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc deleted file mode 100644 index 50cf5a50d7a8..000000000000 --- a/startop/view_compiler/dex_builder.cc +++ /dev/null @@ -1,704 +0,0 @@ -/* - * Copyright (C) 2018 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 "dex_builder.h" - -#include <fstream> -#include <memory> - -namespace startop { -namespace dex { - -using std::shared_ptr; -using std::string; - -using ::dex::kAccPublic; -using Op = Instruction::Op; - -const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; }; -const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; }; - -namespace { -// From https://source.android.com/devices/tech/dalvik/dex-format#dex-file-magic -constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00}; - -// Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes. -constexpr size_t kMaxEncodedStringLength{5}; - -// Converts invoke-* to invoke-*/range -constexpr ::dex::Opcode InvokeToInvokeRange(::dex::Opcode opcode) { - switch (opcode) { - case ::dex::Opcode::OP_INVOKE_VIRTUAL: - return ::dex::Opcode::OP_INVOKE_VIRTUAL_RANGE; - case ::dex::Opcode::OP_INVOKE_DIRECT: - return ::dex::Opcode::OP_INVOKE_DIRECT_RANGE; - case ::dex::Opcode::OP_INVOKE_STATIC: - return ::dex::Opcode::OP_INVOKE_STATIC_RANGE; - case ::dex::Opcode::OP_INVOKE_INTERFACE: - return ::dex::Opcode::OP_INVOKE_INTERFACE_RANGE; - default: - LOG(FATAL) << opcode << " is not a recognized invoke opcode."; - __builtin_unreachable(); - } -} - -std::string DotToDescriptor(const char* class_name) { - std::string descriptor(class_name); - std::replace(descriptor.begin(), descriptor.end(), '.', '/'); - if (descriptor.length() > 0 && descriptor[0] != '[') { - descriptor = "L" + descriptor + ";"; - } - return descriptor; -} - -} // namespace - -std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { - switch (opcode) { - case Instruction::Op::kReturn: - out << "kReturn"; - return out; - case Instruction::Op::kReturnObject: - out << "kReturnObject"; - return out; - case Instruction::Op::kMove: - out << "kMove"; - return out; - case Instruction::Op::kMoveObject: - out << "kMoveObject"; - return out; - case Instruction::Op::kInvokeVirtual: - out << "kInvokeVirtual"; - return out; - case Instruction::Op::kInvokeDirect: - out << "kInvokeDirect"; - return out; - case Instruction::Op::kInvokeStatic: - out << "kInvokeStatic"; - return out; - case Instruction::Op::kInvokeInterface: - out << "kInvokeInterface"; - return out; - case Instruction::Op::kBindLabel: - out << "kBindLabel"; - return out; - case Instruction::Op::kBranchEqz: - out << "kBranchEqz"; - return out; - case Instruction::Op::kBranchNEqz: - out << "kBranchNEqz"; - return out; - case Instruction::Op::kNew: - out << "kNew"; - return out; - case Instruction::Op::kCheckCast: - out << "kCheckCast"; - return out; - case Instruction::Op::kGetStaticField: - out << "kGetStaticField"; - return out; - case Instruction::Op::kSetStaticField: - out << "kSetStaticField"; - return out; - case Instruction::Op::kGetInstanceField: - out << "kGetInstanceField"; - return out; - case Instruction::Op::kSetInstanceField: - out << "kSetInstanceField"; - return out; - } -} - -std::ostream& operator<<(std::ostream& out, const Value& value) { - if (value.is_register()) { - out << "Register(" << value.value() << ")"; - } else if (value.is_parameter()) { - out << "Parameter(" << value.value() << ")"; - } else if (value.is_immediate()) { - out << "Immediate(" << value.value() << ")"; - } else if (value.is_string()) { - out << "String(" << value.value() << ")"; - } else if (value.is_label()) { - out << "Label(" << value.value() << ")"; - } else if (value.is_type()) { - out << "Type(" << value.value() << ")"; - } else { - out << "UnknownValue"; - } - return out; -} - -void* TrackingAllocator::Allocate(size_t size) { - std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size); - void* raw_buffer = buffer.get(); - allocations_[raw_buffer] = std::move(buffer); - return raw_buffer; -} - -void TrackingAllocator::Free(void* ptr) { allocations_.erase(allocations_.find(ptr)); } - -// Write out a DEX file that is basically: -// -// package dextest; -// public class DexTest { -// public static int foo(String s) { return s.length(); } -// } -void WriteTestDexFile(const string& filename) { - DexBuilder dex_file; - - ClassBuilder cbuilder{dex_file.MakeClass("dextest.DexTest")}; - cbuilder.set_source_file("dextest.java"); - - TypeDescriptor string_type = TypeDescriptor::FromClassname("java.lang.String"); - - MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), string_type})}; - - LiveRegister result = method.AllocRegister(); - - MethodDeclData string_length = - dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()}); - - method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0))); - method.BuildReturn(result); - - method.Encode(); - - slicer::MemView image{dex_file.CreateImage()}; - - std::ofstream out_file(filename); - out_file.write(image.ptr<const char>(), image.size()); -} - -TypeDescriptor TypeDescriptor::FromClassname(const std::string& name) { - return TypeDescriptor{DotToDescriptor(name.c_str())}; -} - -DexBuilder::DexBuilder() : dex_file_{std::make_shared<ir::DexFile>()} { - dex_file_->magic = slicer::MemView{kDexFileMagic, sizeof(kDexFileMagic)}; -} - -slicer::MemView DexBuilder::CreateImage() { - ::dex::Writer writer(dex_file_); - size_t image_size{0}; - ::dex::u1* image = writer.CreateImage(&allocator_, &image_size); - return slicer::MemView{image, image_size}; -} - -ir::String* DexBuilder::GetOrAddString(const std::string& string) { - ir::String*& entry = strings_[string]; - - if (entry == nullptr) { - // Need to encode the length and then write out the bytes, including 1 byte for null terminator - auto buffer = std::make_unique<uint8_t[]>(string.size() + kMaxEncodedStringLength + 1); - uint8_t* string_data_start = ::dex::WriteULeb128(buffer.get(), string.size()); - - size_t header_length = - reinterpret_cast<uintptr_t>(string_data_start) - reinterpret_cast<uintptr_t>(buffer.get()); - - auto end = std::copy(string.begin(), string.end(), string_data_start); - *end = '\0'; - - entry = Alloc<ir::String>(); - // +1 for null terminator - entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1}; - ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex(); - dex_file_->strings_map[new_index] = entry; - entry->orig_index = new_index; - string_data_.push_back(std::move(buffer)); - } - return entry; -} - -ClassBuilder DexBuilder::MakeClass(const std::string& name) { - auto* class_def = Alloc<ir::Class>(); - ir::Type* type_def = GetOrAddType(DotToDescriptor(name.c_str())); - type_def->class_def = class_def; - - class_def->type = type_def; - class_def->super_class = GetOrAddType(DotToDescriptor("java.lang.Object")); - class_def->access_flags = kAccPublic; - return ClassBuilder{this, name, class_def}; -} - -ir::Type* DexBuilder::GetOrAddType(const std::string& descriptor) { - if (types_by_descriptor_.find(descriptor) != types_by_descriptor_.end()) { - return types_by_descriptor_[descriptor]; - } - - ir::Type* type = Alloc<ir::Type>(); - type->descriptor = GetOrAddString(descriptor); - types_by_descriptor_[descriptor] = type; - type->orig_index = dex_file_->types_indexes.AllocateIndex(); - dex_file_->types_map[type->orig_index] = type; - return type; -} - -ir::FieldDecl* DexBuilder::GetOrAddField(TypeDescriptor parent, const std::string& name, - TypeDescriptor type) { - const auto key = std::make_tuple(parent, name); - if (field_decls_by_key_.find(key) != field_decls_by_key_.end()) { - return field_decls_by_key_[key]; - } - - ir::FieldDecl* field = Alloc<ir::FieldDecl>(); - field->parent = GetOrAddType(parent); - field->name = GetOrAddString(name); - field->type = GetOrAddType(type); - field->orig_index = dex_file_->fields_indexes.AllocateIndex(); - dex_file_->fields_map[field->orig_index] = field; - field_decls_by_key_[key] = field; - return field; -} - -ir::Proto* Prototype::Encode(DexBuilder* dex) const { - auto* proto = dex->Alloc<ir::Proto>(); - proto->shorty = dex->GetOrAddString(Shorty()); - proto->return_type = dex->GetOrAddType(return_type_.descriptor()); - if (param_types_.size() > 0) { - proto->param_types = dex->Alloc<ir::TypeList>(); - for (const auto& param_type : param_types_) { - proto->param_types->types.push_back(dex->GetOrAddType(param_type.descriptor())); - } - } else { - proto->param_types = nullptr; - } - return proto; -} - -std::string Prototype::Shorty() const { - std::string shorty; - shorty.append(return_type_.short_descriptor()); - for (const auto& type_descriptor : param_types_) { - shorty.append(type_descriptor.short_descriptor()); - } - return shorty; -} - -const TypeDescriptor& Prototype::ArgType(size_t index) const { - CHECK_LT(index, param_types_.size()); - return param_types_[index]; -} - -ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def) - : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {} - -MethodBuilder ClassBuilder::CreateMethod(const std::string& name, Prototype prototype) { - ir::MethodDecl* decl = parent_->GetOrDeclareMethod(type_descriptor_, name, prototype).decl; - - return MethodBuilder{parent_, class_, decl}; -} - -void ClassBuilder::set_source_file(const string& source) { - class_->source_file = parent_->GetOrAddString(source); -} - -MethodBuilder::MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl) - : dex_{dex}, class_{class_def}, decl_{decl} {} - -ir::EncodedMethod* MethodBuilder::Encode() { - auto* method = dex_->Alloc<ir::EncodedMethod>(); - method->decl = decl_; - - // TODO: make access flags configurable - method->access_flags = kAccPublic | ::dex::kAccStatic; - - auto* code = dex_->Alloc<ir::Code>(); - CHECK(decl_->prototype != nullptr); - size_t const num_args = - decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0; - code->registers = NumRegisters() + num_args + kMaxScratchRegisters; - code->ins_count = num_args; - EncodeInstructions(); - code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size()); - size_t const return_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1; - code->outs_count = std::max(return_count, max_args_); - method->code = code; - - class_->direct_methods.push_back(method); - - return method; -} - -LiveRegister MethodBuilder::AllocRegister() { - // Find a free register - for (size_t i = 0; i < register_liveness_.size(); ++i) { - if (!register_liveness_[i]) { - register_liveness_[i] = true; - return LiveRegister{®ister_liveness_, i}; - } - } - - // If we get here, all the registers are in use, so we have to allocate a new - // one. - register_liveness_.push_back(true); - return LiveRegister{®ister_liveness_, register_liveness_.size() - 1}; -} - -Value MethodBuilder::MakeLabel() { - labels_.push_back({}); - return Value::Label(labels_.size() - 1); -} - -void MethodBuilder::AddInstruction(Instruction instruction) { - instructions_.push_back(instruction); -} - -void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); } - -void MethodBuilder::BuildReturn(Value src, bool is_object) { - AddInstruction(Instruction::OpWithArgs( - is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src)); -} - -void MethodBuilder::BuildConst4(Value target, int value) { - CHECK_LT(value, 16); - AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value))); -} - -void MethodBuilder::BuildConstString(Value target, const std::string& value) { - const ir::String* const dex_string = dex_->GetOrAddString(value); - AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index))); -} - -void MethodBuilder::EncodeInstructions() { - buffer_.clear(); - for (const auto& instruction : instructions_) { - EncodeInstruction(instruction); - } -} - -void MethodBuilder::EncodeInstruction(const Instruction& instruction) { - switch (instruction.opcode()) { - case Instruction::Op::kReturn: - return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN); - case Instruction::Op::kReturnObject: - return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN_OBJECT); - case Instruction::Op::kMove: - case Instruction::Op::kMoveObject: - return EncodeMove(instruction); - case Instruction::Op::kInvokeVirtual: - return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_VIRTUAL); - case Instruction::Op::kInvokeDirect: - return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_DIRECT); - case Instruction::Op::kInvokeStatic: - return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_STATIC); - case Instruction::Op::kInvokeInterface: - return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_INTERFACE); - case Instruction::Op::kBindLabel: - return BindLabel(instruction.args()[0]); - case Instruction::Op::kBranchEqz: - return EncodeBranch(::dex::Opcode::OP_IF_EQZ, instruction); - case Instruction::Op::kBranchNEqz: - return EncodeBranch(::dex::Opcode::OP_IF_NEZ, instruction); - case Instruction::Op::kNew: - return EncodeNew(instruction); - case Instruction::Op::kCheckCast: - return EncodeCast(instruction); - case Instruction::Op::kGetStaticField: - case Instruction::Op::kSetStaticField: - case Instruction::Op::kGetInstanceField: - case Instruction::Op::kSetInstanceField: - return EncodeFieldOp(instruction); - } -} - -void MethodBuilder::EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode) { - CHECK(!instruction.dest().has_value()); - if (instruction.args().size() == 0) { - Encode10x(::dex::Opcode::OP_RETURN_VOID); - } else { - CHECK_EQ(1, instruction.args().size()); - size_t source = RegisterValue(instruction.args()[0]); - Encode11x(opcode, source); - } -} - -void MethodBuilder::EncodeMove(const Instruction& instruction) { - CHECK(Instruction::Op::kMove == instruction.opcode() || - Instruction::Op::kMoveObject == instruction.opcode()); - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(1, instruction.args().size()); - - const Value& source = instruction.args()[0]; - - if (source.is_immediate()) { - // TODO: support more registers - CHECK_LT(RegisterValue(*instruction.dest()), 16); - Encode11n(::dex::Opcode::OP_CONST_4, RegisterValue(*instruction.dest()), source.value()); - } else if (source.is_string()) { - constexpr size_t kMaxRegisters = 256; - CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); - CHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string - Encode21c(::dex::Opcode::OP_CONST_STRING, RegisterValue(*instruction.dest()), source.value()); - } else if (source.is_variable()) { - // For the moment, we only use this when we need to reshuffle registers for - // an invoke instruction, meaning we are too big for the 4-bit version. - // We'll err on the side of caution and always generate the 16-bit form of - // the instruction. - auto opcode = instruction.opcode() == Instruction::Op::kMove - ? ::dex::Opcode::OP_MOVE_16 - : ::dex::Opcode::OP_MOVE_OBJECT_16; - Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source)); - } else { - UNIMPLEMENTED(FATAL); - } -} - -void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode) { - constexpr size_t kMaxArgs = 5; - - // Currently, we only support up to 5 arguments. - CHECK_LE(instruction.args().size(), kMaxArgs); - - uint8_t arguments[kMaxArgs]{}; - bool has_long_args = false; - for (size_t i = 0; i < instruction.args().size(); ++i) { - CHECK(instruction.args()[i].is_variable()); - arguments[i] = RegisterValue(instruction.args()[i]); - if (!IsShortRegister(arguments[i])) { - has_long_args = true; - } - } - - if (has_long_args) { - // Some of the registers don't fit in the four bit short form of the invoke - // instruction, so we need to do an invoke/range. To do this, we need to - // first move all the arguments into contiguous temporary registers. - std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>(); - - const auto& prototype = dex_->GetPrototypeByMethodId(instruction.index_argument()); - CHECK(prototype.has_value()); - - for (size_t i = 0; i < instruction.args().size(); ++i) { - Instruction::Op move_op; - if (opcode == ::dex::Opcode::OP_INVOKE_VIRTUAL || - opcode == ::dex::Opcode::OP_INVOKE_DIRECT) { - // In this case, there is an implicit `this` argument, which is always an object. - if (i == 0) { - move_op = Instruction::Op::kMoveObject; - } else { - move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject - : Instruction::Op::kMove; - } - } else { - move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject - : Instruction::Op::kMove; - } - - EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i])); - } - - Encode3rc(InvokeToInvokeRange(opcode), - instruction.args().size(), - instruction.index_argument(), - RegisterValue(scratch[0])); - } else { - Encode35c(opcode, - instruction.args().size(), - instruction.index_argument(), - arguments[0], - arguments[1], - arguments[2], - arguments[3], - arguments[4]); - } - - // If there is a return value, add a move-result instruction - if (instruction.dest().has_value()) { - Encode11x(instruction.result_is_object() ? ::dex::Opcode::OP_MOVE_RESULT_OBJECT - : ::dex::Opcode::OP_MOVE_RESULT, - RegisterValue(*instruction.dest())); - } - - max_args_ = std::max(max_args_, instruction.args().size()); -} - -// Encodes a conditional branch that tests a single argument. -void MethodBuilder::EncodeBranch(::dex::Opcode op, const Instruction& instruction) { - const auto& args = instruction.args(); - const auto& test_value = args[0]; - const auto& branch_target = args[1]; - CHECK_EQ(2, args.size()); - CHECK(test_value.is_variable()); - CHECK(branch_target.is_label()); - - size_t instruction_offset = buffer_.size(); - size_t field_offset = buffer_.size() + 1; - Encode21c( - op, RegisterValue(test_value), LabelValue(branch_target, instruction_offset, field_offset)); -} - -void MethodBuilder::EncodeNew(const Instruction& instruction) { - CHECK_EQ(Instruction::Op::kNew, instruction.opcode()); - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(1, instruction.args().size()); - - const Value& type = instruction.args()[0]; - CHECK_LT(RegisterValue(*instruction.dest()), 256); - CHECK(type.is_type()); - Encode21c(::dex::Opcode::OP_NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); -} - -void MethodBuilder::EncodeCast(const Instruction& instruction) { - CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(1, instruction.args().size()); - - const Value& type = instruction.args()[0]; - CHECK_LT(RegisterValue(*instruction.dest()), 256); - CHECK(type.is_type()); - Encode21c(::dex::Opcode::OP_CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); -} - -void MethodBuilder::EncodeFieldOp(const Instruction& instruction) { - const auto& args = instruction.args(); - switch (instruction.opcode()) { - case Instruction::Op::kGetStaticField: { - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(0, instruction.args().size()); - - Encode21c(::dex::Opcode::OP_SGET, - RegisterValue(*instruction.dest()), - instruction.index_argument()); - break; - } - case Instruction::Op::kSetStaticField: { - CHECK(!instruction.dest().has_value()); - CHECK_EQ(1, args.size()); - CHECK(args[0].is_variable()); - - Encode21c(::dex::Opcode::OP_SPUT, RegisterValue(args[0]), instruction.index_argument()); - break; - } - case Instruction::Op::kGetInstanceField: { - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(1, instruction.args().size()); - - Encode22c(::dex::Opcode::OP_IGET, - RegisterValue(*instruction.dest()), - RegisterValue(args[0]), - instruction.index_argument()); - break; - } - case Instruction::Op::kSetInstanceField: { - CHECK(!instruction.dest().has_value()); - CHECK_EQ(2, args.size()); - CHECK(args[0].is_variable()); - CHECK(args[1].is_variable()); - - Encode22c(::dex::Opcode::OP_IPUT, - RegisterValue(args[1]), - RegisterValue(args[0]), - instruction.index_argument()); - break; - } - default: { LOG(FATAL) << "Unsupported field operation"; } - } -} - -size_t MethodBuilder::RegisterValue(const Value& value) const { - if (value.is_register()) { - return value.value(); - } else if (value.is_parameter()) { - return value.value() + NumRegisters() + kMaxScratchRegisters; - } - CHECK(false && "Must be either a parameter or a register"); - return 0; -} - -void MethodBuilder::BindLabel(const Value& label_id) { - CHECK(label_id.is_label()); - - LabelData& label = labels_[label_id.value()]; - CHECK(!label.bound_address.has_value()); - - label.bound_address = buffer_.size(); - - // patch any forward references to this label. - for (const auto& ref : label.references) { - buffer_[ref.field_offset] = *label.bound_address - ref.instruction_offset; - } - // No point keeping these around anymore. - label.references.clear(); -} - -::dex::u2 MethodBuilder::LabelValue(const Value& label_id, size_t instruction_offset, - size_t field_offset) { - CHECK(label_id.is_label()); - LabelData& label = labels_[label_id.value()]; - - // Short-circuit if the label is already bound. - if (label.bound_address.has_value()) { - return *label.bound_address - instruction_offset; - } - - // Otherwise, save a reference to where we need to back-patch later. - label.references.push_front(LabelReference{instruction_offset, field_offset}); - return 0; -} - -const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name, - Prototype prototype) { - MethodDeclData& entry = method_id_map_[{type, name, prototype}]; - - if (entry.decl == nullptr) { - // This method has not already been declared, so declare it. - ir::MethodDecl* decl = dex_file_->Alloc<ir::MethodDecl>(); - // The method id is the last added method. - size_t id = dex_file_->methods.size() - 1; - - ir::String* dex_name{GetOrAddString(name)}; - decl->name = dex_name; - decl->parent = GetOrAddType(type.descriptor()); - decl->prototype = GetOrEncodeProto(prototype); - - // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc) - auto new_index = dex_file_->methods_indexes.AllocateIndex(); - auto& ir_node = dex_file_->methods_map[new_index]; - CHECK(ir_node == nullptr); - ir_node = decl; - decl->orig_index = decl->index = new_index; - - entry = {id, decl}; - } - - return entry; -} - -std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const { - for (const auto& entry : method_id_map_) { - if (entry.second.id == method_id) { - return entry.first.prototype; - } - } - return {}; -} - -ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) { - ir::Proto*& ir_proto = proto_map_[prototype]; - if (ir_proto == nullptr) { - ir_proto = prototype.Encode(this); - } - return ir_proto; -} - -} // namespace dex -} // namespace startop diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h deleted file mode 100644 index eb2dc88835d4..000000000000 --- a/startop/view_compiler/dex_builder.h +++ /dev/null @@ -1,626 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#ifndef DEX_BUILDER_H_ -#define DEX_BUILDER_H_ - -#include <array> -#include <forward_list> -#include <map> -#include <optional> -#include <string> -#include <unordered_map> -#include <vector> - -#include "android-base/logging.h" - -#include "slicer/dex_bytecode.h" -#include "slicer/dex_ir.h" -#include "slicer/writer.h" - -namespace startop { -namespace dex { - -// TODO: remove this once the dex generation code is complete. -void WriteTestDexFile(const std::string& filename); - -////////////////////////// -// Forward declarations // -////////////////////////// -class DexBuilder; - -// Our custom allocator for dex::Writer -// -// This keeps track of all allocations and ensures they are freed when -// TrackingAllocator is destroyed. Pointers to memory allocated by this -// allocator must not outlive the allocator. -class TrackingAllocator : public ::dex::Writer::Allocator { - public: - virtual void* Allocate(size_t size); - virtual void Free(void* ptr); - - private: - std::unordered_map<void*, std::unique_ptr<uint8_t[]>> allocations_; -}; - -// Represents a DEX type descriptor. -// -// TODO: add a way to create a descriptor for a reference of a class type. -class TypeDescriptor { - public: - // Named constructors for base type descriptors. - static const TypeDescriptor Int(); - static const TypeDescriptor Void(); - - // Creates a type descriptor from a fully-qualified class name. For example, it turns the class - // name java.lang.Object into the descriptor Ljava/lang/Object. - static TypeDescriptor FromClassname(const std::string& name); - - // Return the full descriptor, such as I or Ljava/lang/Object - const std::string& descriptor() const { return descriptor_; } - // Return the shorty descriptor, such as I or L - std::string short_descriptor() const { return descriptor().substr(0, 1); } - - bool is_object() const { return short_descriptor() == "L"; } - - bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; } - - private: - explicit TypeDescriptor(std::string descriptor) : descriptor_{descriptor} {} - - const std::string descriptor_; -}; - -// Defines a function signature. For example, Prototype{TypeDescriptor::VOID, TypeDescriptor::Int} -// represents the function type (Int) -> Void. -class Prototype { - public: - template <typename... TypeDescriptors> - explicit Prototype(TypeDescriptor return_type, TypeDescriptors... param_types) - : return_type_{return_type}, param_types_{param_types...} {} - - // Encode this prototype into the dex file. - ir::Proto* Encode(DexBuilder* dex) const; - - // Get the shorty descriptor, such as VII for (Int, Int) -> Void - std::string Shorty() const; - - const TypeDescriptor& ArgType(size_t index) const; - - bool operator<(const Prototype& rhs) const { - return std::make_tuple(return_type_, param_types_) < - std::make_tuple(rhs.return_type_, rhs.param_types_); - } - - private: - const TypeDescriptor return_type_; - const std::vector<TypeDescriptor> param_types_; -}; - -// Represents a DEX register or constant. We separate regular registers and parameters -// because we will not know the real parameter id until after all instructions -// have been generated. -class Value { - public: - static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; } - static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; } - static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; } - static constexpr Value String(size_t value) { return Value{value, Kind::kString}; } - static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; } - static constexpr Value Type(size_t id) { return Value{id, Kind::kType}; } - - bool is_register() const { return kind_ == Kind::kLocalRegister; } - bool is_parameter() const { return kind_ == Kind::kParameter; } - bool is_variable() const { return is_register() || is_parameter(); } - bool is_immediate() const { return kind_ == Kind::kImmediate; } - bool is_string() const { return kind_ == Kind::kString; } - bool is_label() const { return kind_ == Kind::kLabel; } - bool is_type() const { return kind_ == Kind::kType; } - - size_t value() const { return value_; } - - constexpr Value() : value_{0}, kind_{Kind::kInvalid} {} - - private: - enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; - - size_t value_; - Kind kind_; - - constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {} -}; - -// Represents an allocated register returned by MethodBuilder::AllocRegister -class LiveRegister { - friend class MethodBuilder; - - public: - LiveRegister(LiveRegister&& other) : liveness_{other.liveness_}, index_{other.index_} { - other.index_ = {}; - }; - ~LiveRegister() { - if (index_.has_value()) { - (*liveness_)[*index_] = false; - } - }; - - operator const Value() const { return Value::Local(*index_); } - - private: - LiveRegister(std::vector<bool>* liveness, size_t index) : liveness_{liveness}, index_{index} {} - - std::vector<bool>* const liveness_; - std::optional<size_t> index_; -}; - -// A virtual instruction. We convert these to real instructions in MethodBuilder::Encode. -// Virtual instructions are needed to keep track of information that is not known until all of the -// code is generated. This information includes things like how many local registers are created and -// branch target locations. -class Instruction { - public: - // The operation performed by this instruction. These are virtual instructions that do not - // correspond exactly to DEX instructions. - enum class Op { - kBindLabel, - kBranchEqz, - kBranchNEqz, - kCheckCast, - kGetInstanceField, - kGetStaticField, - kInvokeDirect, - kInvokeInterface, - kInvokeStatic, - kInvokeVirtual, - kMove, - kMoveObject, - kNew, - kReturn, - kReturnObject, - kSetInstanceField, - kSetStaticField - }; - - //////////////////////// - // Named Constructors // - //////////////////////// - - // For instructions with no return value and no arguments. - static inline Instruction OpNoArgs(Op opcode) { - return Instruction{opcode, /*index_argument*/ 0, /*dest*/ {}}; - } - // For most instructions, which take some number of arguments and have an optional return value. - template <typename... T> - static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, - const T&... args) { - return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...}; - } - - // A cast instruction. Basically, `(type)val` - static inline Instruction Cast(Value val, Value type) { - CHECK(type.is_type()); - return OpWithArgs(Op::kCheckCast, val, type); - } - - // For method calls. - template <typename... T> - static inline Instruction InvokeVirtual(size_t index_argument, std::optional<const Value> dest, - Value this_arg, T... args) { - return Instruction{ - Op::kInvokeVirtual, index_argument, /*result_is_object=*/false, dest, this_arg, args...}; - } - // Returns an object - template <typename... T> - static inline Instruction InvokeVirtualObject(size_t index_argument, - std::optional<const Value> dest, Value this_arg, - const T&... args) { - return Instruction{ - Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...}; - } - // For direct calls (basically, constructors). - template <typename... T> - static inline Instruction InvokeDirect(size_t index_argument, std::optional<const Value> dest, - Value this_arg, const T&... args) { - return Instruction{ - Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...}; - } - // Returns an object - template <typename... T> - static inline Instruction InvokeDirectObject(size_t index_argument, - std::optional<const Value> dest, Value this_arg, - T... args) { - return Instruction{ - Op::kInvokeDirect, index_argument, /*result_is_object=*/true, dest, this_arg, args...}; - } - // For static calls. - template <typename... T> - static inline Instruction InvokeStatic(size_t index_argument, std::optional<const Value> dest, - T... args) { - return Instruction{ - Op::kInvokeStatic, index_argument, /*result_is_object=*/false, dest, args...}; - } - // Returns an object - template <typename... T> - static inline Instruction InvokeStaticObject(size_t index_argument, - std::optional<const Value> dest, T... args) { - return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/true, dest, args...}; - } - // For static calls. - template <typename... T> - static inline Instruction InvokeInterface(size_t index_argument, std::optional<const Value> dest, - const T&... args) { - return Instruction{ - Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...}; - } - - static inline Instruction GetStaticField(size_t field_id, Value dest) { - return Instruction{Op::kGetStaticField, field_id, dest}; - } - - static inline Instruction SetStaticField(size_t field_id, Value value) { - return Instruction{ - Op::kSetStaticField, field_id, /*result_is_object=*/false, /*dest=*/{}, value}; - } - - static inline Instruction GetField(size_t field_id, Value dest, Value object) { - return Instruction{Op::kGetInstanceField, field_id, /*result_is_object=*/false, dest, object}; - } - - static inline Instruction SetField(size_t field_id, Value object, Value value) { - return Instruction{ - Op::kSetInstanceField, field_id, /*result_is_object=*/false, /*dest=*/{}, object, value}; - } - - /////////////// - // Accessors // - /////////////// - - Op opcode() const { return opcode_; } - size_t index_argument() const { return index_argument_; } - bool result_is_object() const { return result_is_object_; } - const std::optional<const Value>& dest() const { return dest_; } - const std::vector<const Value>& args() const { return args_; } - - private: - inline Instruction(Op opcode, size_t index_argument, std::optional<const Value> dest) - : opcode_{opcode}, - index_argument_{index_argument}, - result_is_object_{false}, - dest_{dest}, - args_{} {} - - template <typename... T> - inline Instruction(Op opcode, size_t index_argument, bool result_is_object, - std::optional<const Value> dest, const T&... args) - : opcode_{opcode}, - index_argument_{index_argument}, - result_is_object_{result_is_object}, - dest_{dest}, - args_{args...} {} - - const Op opcode_; - // The index of the method to invoke, for kInvokeVirtual and similar opcodes. - const size_t index_argument_{0}; - const bool result_is_object_; - const std::optional<const Value> dest_; - const std::vector<const Value> args_; -}; - -// Needed for CHECK_EQ, DCHECK_EQ, etc. -std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode); - -// Keeps track of information needed to manipulate or call a method. -struct MethodDeclData { - size_t id; - ir::MethodDecl* decl; -}; - -// Tools to help build methods and their bodies. -class MethodBuilder { - public: - MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl); - - // Encode the method into DEX format. - ir::EncodedMethod* Encode(); - - // Create a new register to be used to storing values. - LiveRegister AllocRegister(); - - Value MakeLabel(); - - ///////////////////////////////// - // Instruction builder methods // - ///////////////////////////////// - - void AddInstruction(Instruction instruction); - - // return-void - void BuildReturn(); - void BuildReturn(Value src, bool is_object = false); - // const/4 - void BuildConst4(Value target, int value); - void BuildConstString(Value target, const std::string& value); - template <typename... T> - void BuildNew(Value target, TypeDescriptor type, Prototype constructor, const T&... args); - - // TODO: add builders for more instructions - - DexBuilder* dex_file() const { return dex_; } - - private: - void EncodeInstructions(); - void EncodeInstruction(const Instruction& instruction); - - // Encodes a return instruction. For instructions with no return value, the opcode field is - // ignored. Otherwise, this specifies which return instruction will be used (return, - // return-object, etc.) - void EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode); - - void EncodeMove(const Instruction& instruction); - void EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode); - void EncodeBranch(::dex::Opcode op, const Instruction& instruction); - void EncodeNew(const Instruction& instruction); - void EncodeCast(const Instruction& instruction); - void EncodeFieldOp(const Instruction& instruction); - - // Low-level instruction format encoding. See - // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of - // formats. - - inline uint8_t ToBits(::dex::Opcode opcode) { - static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode)); - return static_cast<uint8_t>(opcode); - } - - inline void Encode10x(::dex::Opcode opcode) { - // 00|op - static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode)); - buffer_.push_back(ToBits(opcode)); - } - - inline void Encode11x(::dex::Opcode opcode, uint8_t a) { - // aa|op - buffer_.push_back((a << 8) | ToBits(opcode)); - } - - inline void Encode11n(::dex::Opcode opcode, uint8_t a, int8_t b) { - // b|a|op - - // Make sure the fields are in bounds (4 bits for a, 4 bits for b). - CHECK_LT(a, 16); - CHECK_LE(-8, b); - CHECK_LT(b, 8); - - buffer_.push_back(((b & 0xf) << 12) | (a << 8) | ToBits(opcode)); - } - - inline void Encode21c(::dex::Opcode opcode, uint8_t a, uint16_t b) { - // aa|op|bbbb - buffer_.push_back((a << 8) | ToBits(opcode)); - buffer_.push_back(b); - } - - inline void Encode22c(::dex::Opcode opcode, uint8_t a, uint8_t b, uint16_t c) { - // b|a|op|bbbb - CHECK(IsShortRegister(a)); - CHECK(IsShortRegister(b)); - buffer_.push_back((b << 12) | (a << 8) | ToBits(opcode)); - buffer_.push_back(c); - } - - inline void Encode32x(::dex::Opcode opcode, uint16_t a, uint16_t b) { - buffer_.push_back(ToBits(opcode)); - buffer_.push_back(a); - buffer_.push_back(b); - } - - inline void Encode35c(::dex::Opcode opcode, size_t a, uint16_t b, uint8_t c, uint8_t d, - uint8_t e, uint8_t f, uint8_t g) { - // a|g|op|bbbb|f|e|d|c - - CHECK_LE(a, 5); - CHECK(IsShortRegister(c)); - CHECK(IsShortRegister(d)); - CHECK(IsShortRegister(e)); - CHECK(IsShortRegister(f)); - CHECK(IsShortRegister(g)); - buffer_.push_back((a << 12) | (g << 8) | ToBits(opcode)); - buffer_.push_back(b); - buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c); - } - - inline void Encode3rc(::dex::Opcode opcode, size_t a, uint16_t b, uint16_t c) { - CHECK_LE(a, 255); - buffer_.push_back((a << 8) | ToBits(opcode)); - buffer_.push_back(b); - buffer_.push_back(c); - } - - static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; } - - // Returns an array of num_regs scratch registers. These are guaranteed to be - // contiguous, so they are suitable for the invoke-*/range instructions. - template <int num_regs> - std::array<Value, num_regs> GetScratchRegisters() const { - static_assert(num_regs <= kMaxScratchRegisters); - std::array<Value, num_regs> regs; - for (size_t i = 0; i < num_regs; ++i) { - regs[i] = std::move(Value::Local(NumRegisters() + i)); - } - return regs; - } - - // Converts a register or parameter to its DEX register number. - size_t RegisterValue(const Value& value) const; - - // Sets a label's address to the current position in the instruction buffer. If there are any - // forward references to the label, this function will back-patch them. - void BindLabel(const Value& label); - - // Returns the offset of the label relative to the given instruction offset. If the label is not - // bound, a reference will be saved and it will automatically be patched when the label is bound. - ::dex::u2 LabelValue(const Value& label, size_t instruction_offset, size_t field_offset); - - DexBuilder* dex_; - ir::Class* class_; - ir::MethodDecl* decl_; - - // A list of the instructions we will eventually encode. - std::vector<Instruction> instructions_; - - // A buffer to hold instructions that have been encoded. - std::vector<::dex::u2> buffer_; - - // We create some scratch registers for when we have to shuffle registers - // around to make legal DEX code. - static constexpr size_t kMaxScratchRegisters = 5; - - size_t NumRegisters() const { - return register_liveness_.size(); - } - - // Stores information needed to back-patch a label once it is bound. We need to know the start of - // the instruction that refers to the label, and the offset to where the actual label value should - // go. - struct LabelReference { - size_t instruction_offset; - size_t field_offset; - }; - - struct LabelData { - std::optional<size_t> bound_address; - std::forward_list<LabelReference> references; - }; - - std::vector<LabelData> labels_; - - // During encoding, keep track of the largest number of arguments needed, so we can use it for our - // outs count - size_t max_args_{0}; - - std::vector<bool> register_liveness_; -}; - -// A helper to build class definitions. -class ClassBuilder { - public: - ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def); - - void set_source_file(const std::string& source); - - // Create a method with the given name and prototype. The returned MethodBuilder can be used to - // fill in the method body. - MethodBuilder CreateMethod(const std::string& name, Prototype prototype); - - private: - DexBuilder* const parent_; - const TypeDescriptor type_descriptor_; - ir::Class* const class_; -}; - -// Builds Dex files from scratch. -class DexBuilder { - public: - DexBuilder(); - - // Create an in-memory image of the DEX file that can either be loaded directly or written to a - // file. - slicer::MemView CreateImage(); - - template <typename T> - T* Alloc() { - return dex_file_->Alloc<T>(); - } - - // Find the ir::String that matches the given string, creating it if it does not exist. - ir::String* GetOrAddString(const std::string& string); - // Create a new class of the given name. - ClassBuilder MakeClass(const std::string& name); - - // Add a type for the given descriptor, or return the existing one if it already exists. - // See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare - // imported classes. - ir::Type* GetOrAddType(const std::string& descriptor); - inline ir::Type* GetOrAddType(TypeDescriptor descriptor) { - return GetOrAddType(descriptor.descriptor()); - } - - ir::FieldDecl* GetOrAddField(TypeDescriptor parent, const std::string& name, TypeDescriptor type); - - // Returns the method id for the method, creating it if it has not been created yet. - const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name, - Prototype prototype); - - std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const; - - private: - // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not - // exist. - ir::Proto* GetOrEncodeProto(Prototype prototype); - - std::shared_ptr<ir::DexFile> dex_file_; - - // allocator_ is needed to be able to encode the image. - TrackingAllocator allocator_; - - // We'll need to allocate buffers for all of the encoded strings we create. This is where we store - // all of them. - std::vector<std::unique_ptr<uint8_t[]>> string_data_; - - // Keep track of what types we've defined so we can look them up later. - std::unordered_map<std::string, ir::Type*> types_by_descriptor_; - - struct MethodDescriptor { - TypeDescriptor type; - std::string name; - Prototype prototype; - - inline bool operator<(const MethodDescriptor& rhs) const { - return std::make_tuple(type, name, prototype) < - std::make_tuple(rhs.type, rhs.name, rhs.prototype); - } - }; - - // Maps method declarations to their method index. This is needed to encode references to them. - // When we go to actually write the DEX file, slicer will re-assign these after correctly sorting - // the methods list. - std::map<MethodDescriptor, MethodDeclData> method_id_map_; - - // Keep track of what strings we've defined so we can look them up later. - std::unordered_map<std::string, ir::String*> strings_; - - // Keep track of already-encoded protos. - std::map<Prototype, ir::Proto*> proto_map_; - - // Keep track of fields that have been declared - std::map<std::tuple<TypeDescriptor, std::string>, ir::FieldDecl*> field_decls_by_key_; -}; - -template <typename... T> -void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor, - const T&... args) { - MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)}; - // allocate the object - ir::Type* type_def = dex_->GetOrAddType(type.descriptor()); - AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kNew, target, Value::Type(type_def->orig_index))); - // call the constructor - AddInstruction(Instruction::InvokeDirect(constructor_data.id, /*dest=*/{}, target, args...)); -}; - -} // namespace dex -} // namespace startop - -#endif // DEX_BUILDER_H_ diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp deleted file mode 100644 index bcba2febdcdc..000000000000 --- a/startop/view_compiler/dex_builder_test/Android.bp +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -genrule { - name: "generate_compiled_layout1", - tools: [":viewcompiler"], - cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", - srcs: ["res/layout/layout1.xml"], - out: [ - "layout1.dex", - ], -} - -genrule { - name: "generate_compiled_layout2", - tools: [":viewcompiler"], - cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", - srcs: ["res/layout/layout2.xml"], - out: [ - "layout2.dex", - ], -} - -android_test { - name: "dex-builder-test", - srcs: [ - "src/android/startop/test/DexBuilderTest.java", - "src/android/startop/test/LayoutCompilerTest.java", - "src/android/startop/test/TestClass.java", - ], - sdk_version: "current", - data: [ - ":generate_dex_testcases", - ":generate_compiled_layout1", - ":generate_compiled_layout2", - ], - static_libs: [ - "androidx.test.core", - "androidx.test.runner", - "junit", - ], - manifest: "AndroidManifest.xml", - resource_dirs: ["res"], - test_config: "AndroidTest.xml", - test_suites: ["general-tests"], -} diff --git a/startop/view_compiler/dex_builder_test/AndroidManifest.xml b/startop/view_compiler/dex_builder_test/AndroidManifest.xml deleted file mode 100644 index b33566363286..000000000000 --- a/startop/view_compiler/dex_builder_test/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.startop.test" > - - <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> - - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.startop.test" - android:label="DexBuilder Tests"/> - -</manifest> diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml deleted file mode 100644 index 59093c79bd0d..000000000000 --- a/startop/view_compiler/dex_builder_test/AndroidTest.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Runs DexBuilder Tests."> - <option name="test-suite-tag" value="apct" /> - <option name="test-suite-tag" value="apct-instrumentation" /> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="dex-builder-test.apk" /> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" /> - <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" /> - <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" /> - <option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" /> - </target_preparer> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="android.startop.test" /> - <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - </test> -</configuration> diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout1.xml b/startop/view_compiler/dex_builder_test/res/layout/layout1.xml deleted file mode 100644 index 0f9375c6ebce..000000000000 --- a/startop/view_compiler/dex_builder_test/res/layout/layout1.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:orientation="vertical" - android:gravity="center"> - - <Button - android:layout_width="match_parent" - android:layout_height="match_parent"/> - <Button - android:layout_width="match_parent" - android:layout_height="match_parent"/> - - </LinearLayout> diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml deleted file mode 100644 index b092e1c20311..000000000000 --- a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Button" /> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Button" /> - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Button" /> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Button" /> - </TableRow> - - </TableRow> - </TableRow> - </TableRow> -</LinearLayout> diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java deleted file mode 100644 index 6af01f6f3292..000000000000 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2018 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.startop.test; - -import android.content.Context; -import androidx.test.InstrumentationRegistry; -import dalvik.system.PathClassLoader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import org.junit.Assert; -import org.junit.Test; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -// Adding tests here requires changes in several other places. See README.md in -// the view_compiler directory for more information. -public final class DexBuilderTest { - static ClassLoader loadDexFile(String filename) throws Exception { - return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename, - DexBuilderTest.class.getClassLoader()); - } - - public void hello() {} - - @Test - public void loadTrivialDex() throws Exception { - ClassLoader loader = loadDexFile("trivial.dex"); - loader.loadClass("android.startop.test.testcases.Trivial"); - } - - @Test - public void return5() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("return5"); - Assert.assertEquals(5, method.invoke(null)); - } - - @Test - public void returnInteger5() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnInteger5"); - Assert.assertEquals(5, method.invoke(null)); - } - - @Test - public void returnParam() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnParam", int.class); - Assert.assertEquals(5, method.invoke(null, 5)); - Assert.assertEquals(42, method.invoke(null, 42)); - } - - @Test - public void returnStringLength() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnStringLength", String.class); - Assert.assertEquals(13, method.invoke(null, "Hello, World!")); - } - - @Test - public void returnIfZero() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnIfZero", int.class); - Assert.assertEquals(5, method.invoke(null, 0)); - Assert.assertEquals(3, method.invoke(null, 17)); - } - - @Test - public void returnIfNotZero() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnIfNotZero", int.class); - Assert.assertEquals(3, method.invoke(null, 0)); - Assert.assertEquals(5, method.invoke(null, 17)); - } - - @Test - public void backwardsBranch() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("backwardsBranch"); - Assert.assertEquals(2, method.invoke(null)); - } - - @Test - public void returnNull() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnNull"); - Assert.assertEquals(null, method.invoke(null)); - } - - @Test - public void makeString() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("makeString"); - Assert.assertEquals("Hello, World!", method.invoke(null)); - } - - @Test - public void returnStringIfZeroAB() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnStringIfZeroAB", int.class); - Assert.assertEquals("a", method.invoke(null, 0)); - Assert.assertEquals("b", method.invoke(null, 1)); - } - - @Test - public void returnStringIfZeroBA() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("returnStringIfZeroBA", int.class); - Assert.assertEquals("b", method.invoke(null, 0)); - Assert.assertEquals("a", method.invoke(null, 1)); - } - - @Test - public void invokeStaticReturnObject() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("invokeStaticReturnObject", int.class, int.class); - Assert.assertEquals("10", method.invoke(null, 10, 10)); - Assert.assertEquals("a", method.invoke(null, 10, 16)); - Assert.assertEquals("5", method.invoke(null, 5, 16)); - } - - @Test - public void invokeVirtualReturnObject() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class); - Assert.assertEquals("bc", method.invoke(null, "abc", 1)); - } - - @Test - public void castObjectToString() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("castObjectToString", Object.class); - Assert.assertEquals("abc", method.invoke(null, "abc")); - boolean castFailed = false; - try { - method.invoke(null, 5); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof ClassCastException) { - castFailed = true; - } else { - throw e; - } - } - Assert.assertTrue(castFailed); - } - - @Test - public void readStaticField() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("readStaticField"); - TestClass.staticInteger = 5; - Assert.assertEquals(5, method.invoke(null)); - } - - @Test - public void setStaticField() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("setStaticField"); - TestClass.staticInteger = 5; - method.invoke(null); - Assert.assertEquals(7, TestClass.staticInteger); - } - - @Test - public void readInstanceField() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("readInstanceField", TestClass.class); - TestClass obj = new TestClass(); - obj.instanceField = 5; - Assert.assertEquals(5, method.invoke(null, obj)); - } - - @Test - public void setInstanceField() throws Exception { - ClassLoader loader = loadDexFile("simple.dex"); - Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); - Method method = clazz.getMethod("setInstanceField", TestClass.class); - TestClass obj = new TestClass(); - obj.instanceField = 5; - method.invoke(null, obj); - Assert.assertEquals(7, obj.instanceField); - } -} diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java deleted file mode 100644 index b0cf91d5fb97..000000000000 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 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.startop.test; - -import android.content.Context; -import androidx.test.InstrumentationRegistry; -import android.view.View; -import dalvik.system.PathClassLoader; -import java.lang.reflect.Method; -import org.junit.Assert; -import org.junit.Test; - -import java.lang.reflect.Method; - -// Adding tests here requires changes in several other places. See README.md in -// the view_compiler directory for more information. -public class LayoutCompilerTest { - static ClassLoader loadDexFile(String filename) throws Exception { - return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename, - ClassLoader.getSystemClassLoader()); - } - - @Test - public void loadAndInflateLayout1() throws Exception { - ClassLoader dex_file = loadDexFile("layout1.dex"); - Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); - Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class); - Context context = InstrumentationRegistry.getTargetContext(); - layout1.invoke(null, context, R.layout.layout1); - } - - @Test - public void loadAndInflateLayout2() throws Exception { - ClassLoader dex_file = loadDexFile("layout2.dex"); - Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); - Method layout1 = compiled_view.getMethod("layout2", Context.class, int.class); - Context context = InstrumentationRegistry.getTargetContext(); - layout1.invoke(null, context, R.layout.layout1); - } -} diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java deleted file mode 100644 index dd7792306030..000000000000 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2018 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.startop.test; - - /** - * A simple class to help test DexBuilder. - */ -public final class TestClass { - public static int staticInteger; - - public int instanceField; -} diff --git a/startop/view_compiler/dex_layout_compiler.cc b/startop/view_compiler/dex_layout_compiler.cc deleted file mode 100644 index bddb8aa6716a..000000000000 --- a/startop/view_compiler/dex_layout_compiler.cc +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2018 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 "dex_layout_compiler.h" -#include "layout_validation.h" - -#include "android-base/stringprintf.h" - -namespace startop { - -using android::base::StringPrintf; -using dex::Instruction; -using dex::LiveRegister; -using dex::Prototype; -using dex::TypeDescriptor; -using dex::Value; - -namespace { -// TODO: these are a bunch of static initializers, which we should avoid. See if -// we can make them constexpr. -const TypeDescriptor kAttributeSet = TypeDescriptor::FromClassname("android.util.AttributeSet"); -const TypeDescriptor kContext = TypeDescriptor::FromClassname("android.content.Context"); -const TypeDescriptor kLayoutInflater = TypeDescriptor::FromClassname("android.view.LayoutInflater"); -const TypeDescriptor kResources = TypeDescriptor::FromClassname("android.content.res.Resources"); -const TypeDescriptor kString = TypeDescriptor::FromClassname("java.lang.String"); -const TypeDescriptor kView = TypeDescriptor::FromClassname("android.view.View"); -const TypeDescriptor kViewGroup = TypeDescriptor::FromClassname("android.view.ViewGroup"); -const TypeDescriptor kXmlResourceParser = - TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"); -} // namespace - -DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method) - : method_{method}, - context_{Value::Parameter(0)}, - resid_{Value::Parameter(1)}, - inflater_{method->AllocRegister()}, - xml_{method->AllocRegister()}, - attrs_{method->AllocRegister()}, - classname_tmp_{method->AllocRegister()}, - xml_next_{method->dex_file()->GetOrDeclareMethod(kXmlResourceParser, "next", - Prototype{TypeDescriptor::Int()})}, - try_create_view_{method->dex_file()->GetOrDeclareMethod( - kLayoutInflater, "tryCreateView", - Prototype{kView, kView, kString, kContext, kAttributeSet})}, - generate_layout_params_{method->dex_file()->GetOrDeclareMethod( - kViewGroup, "generateLayoutParams", - Prototype{TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"), - kAttributeSet})}, - add_view_{method->dex_file()->GetOrDeclareMethod( - kViewGroup, "addView", - Prototype{TypeDescriptor::Void(), - kView, - TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})} {} - -void DexViewBuilder::BuildGetLayoutInflater(Value dest) { - // dest = LayoutInflater.from(context); - auto layout_inflater_from = method_->dex_file()->GetOrDeclareMethod( - kLayoutInflater, "from", Prototype{kLayoutInflater, kContext}); - method_->AddInstruction(Instruction::InvokeStaticObject(layout_inflater_from.id, dest, context_)); -} - -void DexViewBuilder::BuildGetResources(Value dest) { - // dest = context.getResources(); - auto get_resources = - method_->dex_file()->GetOrDeclareMethod(kContext, "getResources", Prototype{kResources}); - method_->AddInstruction(Instruction::InvokeVirtualObject(get_resources.id, dest, context_)); -} - -void DexViewBuilder::BuildGetLayoutResource(Value dest, Value resources, Value resid) { - // dest = resources.getLayout(resid); - auto get_layout = method_->dex_file()->GetOrDeclareMethod( - kResources, "getLayout", Prototype{kXmlResourceParser, TypeDescriptor::Int()}); - method_->AddInstruction(Instruction::InvokeVirtualObject(get_layout.id, dest, resources, resid)); -} - -void DexViewBuilder::BuildLayoutResourceToAttributeSet(dex::Value dest, - dex::Value layout_resource) { - // dest = Xml.asAttributeSet(layout_resource); - auto as_attribute_set = method_->dex_file()->GetOrDeclareMethod( - TypeDescriptor::FromClassname("android.util.Xml"), - "asAttributeSet", - Prototype{kAttributeSet, TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")}); - method_->AddInstruction( - Instruction::InvokeStaticObject(as_attribute_set.id, dest, layout_resource)); -} - -void DexViewBuilder::BuildXmlNext() { - // xml_.next(); - method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_)); -} - -void DexViewBuilder::Start() { - BuildGetLayoutInflater(/*dest=*/inflater_); - BuildGetResources(/*dest=*/xml_); - BuildGetLayoutResource(/*dest=*/xml_, /*resources=*/xml_, resid_); - BuildLayoutResourceToAttributeSet(/*dest=*/attrs_, /*layout_resource=*/xml_); - - // Advance past start document tag - BuildXmlNext(); -} - -void DexViewBuilder::Finish() {} - -namespace { -std::string ResolveName(const std::string& name) { - if (name == "View") return "android.view.View"; - if (name == "ViewGroup") return "android.view.ViewGroup"; - if (name.find('.') == std::string::npos) { - return StringPrintf("android.widget.%s", name.c_str()); - } - return name; -} -} // namespace - -void DexViewBuilder::BuildTryCreateView(Value dest, Value parent, Value classname) { - // dest = inflater_.tryCreateView(parent, classname, context_, attrs_); - method_->AddInstruction(Instruction::InvokeVirtualObject( - try_create_view_.id, dest, inflater_, parent, classname, context_, attrs_)); -} - -void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) { - bool const is_root_view = view_stack_.empty(); - - // Advance to start tag - BuildXmlNext(); - - LiveRegister view = AcquireRegister(); - // try to create the view using the factories - method_->BuildConstString(classname_tmp_, - name); // TODO: the need to fully qualify the classname - if (is_root_view) { - LiveRegister null = AcquireRegister(); - method_->BuildConst4(null, 0); - BuildTryCreateView(/*dest=*/view, /*parent=*/null, classname_tmp_); - } else { - BuildTryCreateView(/*dest=*/view, /*parent=*/GetCurrentView(), classname_tmp_); - } - auto label = method_->MakeLabel(); - // branch if not null - method_->AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label)); - - // If null, create the class directly. - method_->BuildNew(view, - TypeDescriptor::FromClassname(ResolveName(name)), - Prototype{TypeDescriptor::Void(), kContext, kAttributeSet}, - context_, - attrs_); - - method_->AddInstruction(Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, label)); - - if (is_viewgroup) { - // Cast to a ViewGroup so we can add children later. - const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(kViewGroup.descriptor()); - method_->AddInstruction(Instruction::Cast(view, Value::Type(view_group_def->orig_index))); - } - - if (!is_root_view) { - // layout_params = parent.generateLayoutParams(attrs); - LiveRegister layout_params{AcquireRegister()}; - method_->AddInstruction(Instruction::InvokeVirtualObject( - generate_layout_params_.id, layout_params, GetCurrentView(), attrs_)); - view_stack_.push_back({std::move(view), std::move(layout_params)}); - } else { - view_stack_.push_back({std::move(view), {}}); - } -} - -void DexViewBuilder::FinishView() { - if (view_stack_.size() == 1) { - method_->BuildReturn(GetCurrentView(), /*is_object=*/true); - } else { - // parent.add(view, layout_params) - method_->AddInstruction(Instruction::InvokeVirtual( - add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams())); - // xml.next(); // end tag - method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_)); - } - PopViewStack(); -} - -LiveRegister DexViewBuilder::AcquireRegister() { return method_->AllocRegister(); } - -Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; } -Value DexViewBuilder::GetCurrentLayoutParams() const { - return view_stack_.back().layout_params.value(); -} -Value DexViewBuilder::GetParentView() const { return view_stack_[view_stack_.size() - 2].view; } - -void DexViewBuilder::PopViewStack() { - // Unconditionally release the view register. - view_stack_.pop_back(); -} - -} // namespace startop diff --git a/startop/view_compiler/dex_layout_compiler.h b/startop/view_compiler/dex_layout_compiler.h deleted file mode 100644 index a34ed1f0168e..000000000000 --- a/startop/view_compiler/dex_layout_compiler.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#ifndef DEX_LAYOUT_COMPILER_H_ -#define DEX_LAYOUT_COMPILER_H_ - -#include "dex_builder.h" - -#include <codecvt> -#include <locale> -#include <string> -#include <vector> - -namespace startop { - -// This visitor does the actual view compilation, using a supplied builder. -template <typename Builder> -class LayoutCompilerVisitor { - public: - explicit LayoutCompilerVisitor(Builder* builder) : builder_{builder} {} - - void VisitStartDocument() { builder_->Start(); } - void VisitEndDocument() { builder_->Finish(); } - void VisitStartTag(const std::u16string& name) { - parent_stack_.push_back(ViewEntry{ - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(name), {}}); - } - void VisitEndTag() { - auto entry = parent_stack_.back(); - parent_stack_.pop_back(); - - if (parent_stack_.empty()) { - GenerateCode(entry); - } else { - parent_stack_.back().children.push_back(entry); - } - } - - private: - struct ViewEntry { - std::string name; - std::vector<ViewEntry> children; - }; - - void GenerateCode(const ViewEntry& view) { - builder_->StartView(view.name, !view.children.empty()); - for (const auto& child : view.children) { - GenerateCode(child); - } - builder_->FinishView(); - } - - Builder* builder_; - - std::vector<ViewEntry> parent_stack_; -}; - -class DexViewBuilder { - public: - DexViewBuilder(dex::MethodBuilder* method); - - void Start(); - void Finish(); - void StartView(const std::string& name, bool is_viewgroup); - void FinishView(); - - private: - // Accessors for the stack of views that are under construction. - dex::LiveRegister AcquireRegister(); - dex::Value GetCurrentView() const; - dex::Value GetCurrentLayoutParams() const; - dex::Value GetParentView() const; - void PopViewStack(); - - // Methods to simplify building different code fragments. - void BuildGetLayoutInflater(dex::Value dest); - void BuildGetResources(dex::Value dest); - void BuildGetLayoutResource(dex::Value dest, dex::Value resources, dex::Value resid); - void BuildLayoutResourceToAttributeSet(dex::Value dest, dex::Value layout_resource); - void BuildXmlNext(); - void BuildTryCreateView(dex::Value dest, dex::Value parent, dex::Value classname); - - dex::MethodBuilder* method_; - - // Parameters to the generated method - dex::Value const context_; - dex::Value const resid_; - - // Registers used for code generation - const dex::LiveRegister inflater_; - const dex::LiveRegister xml_; - const dex::LiveRegister attrs_; - const dex::LiveRegister classname_tmp_; - - const dex::MethodDeclData xml_next_; - const dex::MethodDeclData try_create_view_; - const dex::MethodDeclData generate_layout_params_; - const dex::MethodDeclData add_view_; - - // Keep track of the views currently in progress. - struct ViewEntry { - dex::LiveRegister view; - std::optional<dex::LiveRegister> layout_params; - }; - std::vector<ViewEntry> view_stack_; -}; - -} // namespace startop - -#endif // DEX_LAYOUT_COMPILER_H_ diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc deleted file mode 100644 index 5dda59e3473f..000000000000 --- a/startop/view_compiler/dex_testcase_generator.cc +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) 2018 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-base/logging.h" -#include "dex_builder.h" - -#include <fstream> -#include <string> - -// Adding tests here requires changes in several other places. See README.md in -// the view_compiler directory for more information. - -using namespace startop::dex; -using namespace std; - -void GenerateTrivialDexFile(const string& outdir) { - DexBuilder dex_file; - - ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.Trivial")}; - cbuilder.set_source_file("dex_testcase_generator.cc#GenerateTrivialDexFile"); - - slicer::MemView image{dex_file.CreateImage()}; - std::ofstream out_file(outdir + "/trivial.dex"); - out_file.write(image.ptr<const char>(), image.size()); -} - -// Generates test cases that test around 1 instruction. -void GenerateSimpleTestCases(const string& outdir) { - DexBuilder dex_file; - - ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.SimpleTests")}; - cbuilder.set_source_file("dex_testcase_generator.cc#GenerateSimpleTestCases"); - - // int return5() { return 5; } - auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})}; - { - LiveRegister r{return5.AllocRegister()}; - return5.BuildConst4(r, 5); - return5.BuildReturn(r); - } - return5.Encode(); - - // int return5() { return 5; } - auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")}; - auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})}; - [&](MethodBuilder& method) { - LiveRegister five{method.AllocRegister()}; - method.BuildConst4(five, 5); - LiveRegister object{method.AllocRegister()}; - method.BuildNew( - object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five); - method.BuildReturn(object, /*is_object=*/true); - }(returnInteger5); - returnInteger5.Encode(); - - // // int returnParam(int x) { return x; } - auto returnParam{cbuilder.CreateMethod("returnParam", - Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})}; - returnParam.BuildReturn(Value::Parameter(0)); - returnParam.Encode(); - - // int returnStringLength(String x) { return x.length(); } - auto string_type{TypeDescriptor::FromClassname("java.lang.String")}; - MethodDeclData string_length{ - dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()})}; - - auto returnStringLength{ - cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})}; - { - LiveRegister result = returnStringLength.AllocRegister(); - returnStringLength.AddInstruction( - Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0))); - returnStringLength.BuildReturn(result); - } - returnStringLength.Encode(); - - // int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } } - MethodBuilder returnIfZero{cbuilder.CreateMethod( - "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})}; - { - LiveRegister resultIfZero{returnIfZero.AllocRegister()}; - Value else_target{returnIfZero.MakeLabel()}; - returnIfZero.AddInstruction(Instruction::OpWithArgs( - Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); - // else branch - returnIfZero.BuildConst4(resultIfZero, 3); - returnIfZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); - // then branch - returnIfZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); - returnIfZero.BuildConst4(resultIfZero, 5); - returnIfZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); - } - returnIfZero.Encode(); - - // int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } } - MethodBuilder returnIfNotZero{cbuilder.CreateMethod( - "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})}; - { - LiveRegister resultIfNotZero{returnIfNotZero.AllocRegister()}; - Value else_target{returnIfNotZero.MakeLabel()}; - returnIfNotZero.AddInstruction(Instruction::OpWithArgs( - Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target)); - // else branch - returnIfNotZero.BuildConst4(resultIfNotZero, 3); - returnIfNotZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero)); - // then branch - returnIfNotZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); - returnIfNotZero.BuildConst4(resultIfNotZero, 5); - returnIfNotZero.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero)); - } - returnIfNotZero.Encode(); - - // Make sure backwards branches work too. - // - // Pseudo code for test: - // { - // zero = 0; - // result = 1; - // if (zero == 0) goto B; - // A: - // return result; - // B: - // result = 2; - // if (zero == 0) goto A; - // result = 3; - // return result; - // } - // If it runs correctly, this test should return 2. - MethodBuilder backwardsBranch{ - cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})}; - [](MethodBuilder& method) { - LiveRegister zero = method.AllocRegister(); - LiveRegister result = method.AllocRegister(); - Value labelA = method.MakeLabel(); - Value labelB = method.MakeLabel(); - method.BuildConst4(zero, 0); - method.BuildConst4(result, 1); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB)); - - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA)); - method.BuildReturn(result); - - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB)); - method.BuildConst4(result, 2); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA)); - - method.BuildConst4(result, 3); - method.BuildReturn(result); - }(backwardsBranch); - backwardsBranch.Encode(); - - // Test that we can make a null value. Basically: - // - // public static String returnNull() { return null; } - MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})}; - [](MethodBuilder& method) { - LiveRegister zero = method.AllocRegister(); - method.BuildConst4(zero, 0); - method.BuildReturn(zero, /*is_object=*/true); - }(returnNull); - returnNull.Encode(); - - // Test that we can make String literals. Basically: - // - // public static String makeString() { return "Hello, World!"; } - MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})}; - [](MethodBuilder& method) { - LiveRegister string = method.AllocRegister(); - method.BuildConstString(string, "Hello, World!"); - method.BuildReturn(string, /*is_object=*/true); - }(makeString); - makeString.Encode(); - - // Make sure strings are sorted correctly. - // - // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } } - MethodBuilder returnStringIfZeroAB{ - cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})}; - [&](MethodBuilder& method) { - LiveRegister resultIfZero{method.AllocRegister()}; - Value else_target{method.MakeLabel()}; - method.AddInstruction(Instruction::OpWithArgs( - Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); - // else branch - method.BuildConstString(resultIfZero, "b"); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); - // then branch - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); - method.BuildConstString(resultIfZero, "a"); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); - method.Encode(); - }(returnStringIfZeroAB); - // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } } - MethodBuilder returnStringIfZeroBA{ - cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})}; - [&](MethodBuilder& method) { - LiveRegister resultIfZero{method.AllocRegister()}; - Value else_target{method.MakeLabel()}; - method.AddInstruction(Instruction::OpWithArgs( - Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); - // else branch - method.BuildConstString(resultIfZero, "a"); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); - // then branch - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); - method.BuildConstString(resultIfZero, "b"); - method.AddInstruction( - Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); - method.Encode(); - }(returnStringIfZeroBA); - - // Make sure we can invoke static methods that return an object - // String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n, - // radix); } - MethodBuilder invokeStaticReturnObject{ - cbuilder.CreateMethod("invokeStaticReturnObject", - Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})}; - [&](MethodBuilder& method) { - LiveRegister result{method.AllocRegister()}; - MethodDeclData to_string{dex_file.GetOrDeclareMethod( - TypeDescriptor::FromClassname("java.lang.Integer"), - "toString", - Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})}; - method.AddInstruction(Instruction::InvokeStaticObject( - to_string.id, result, Value::Parameter(0), Value::Parameter(1))); - method.BuildReturn(result, /*is_object=*/true); - method.Encode(); - }(invokeStaticReturnObject); - - // Make sure we can invoke virtual methods that return an object - // String invokeVirtualReturnObject(String s, int n) { return s.substring(n); } - MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod( - "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})}; - [&](MethodBuilder& method) { - LiveRegister result{method.AllocRegister()}; - MethodDeclData substring{dex_file.GetOrDeclareMethod( - string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})}; - method.AddInstruction(Instruction::InvokeVirtualObject( - substring.id, result, Value::Parameter(0), Value::Parameter(1))); - method.BuildReturn(result, /*is_object=*/true); - method.Encode(); - }(invokeVirtualReturnObject); - - // Make sure we can cast objects - // String castObjectToString(Object o) { return (String)o; } - MethodBuilder castObjectToString{cbuilder.CreateMethod( - "castObjectToString", - Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})}; - [&](MethodBuilder& method) { - const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor()); - method.AddInstruction( - Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index))); - method.BuildReturn(Value::Parameter(0), /*is_object=*/true); - method.Encode(); - }(castObjectToString); - - TypeDescriptor test_class = TypeDescriptor::FromClassname("android.startop.test.TestClass"); - - // Read a static field - // int readStaticField() { return TestClass.staticInteger; } - MethodBuilder readStaticField{ - cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})}; - [&](MethodBuilder& method) { - const ir::FieldDecl* field = - dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int()); - LiveRegister result{method.AllocRegister()}; - method.AddInstruction(Instruction::GetStaticField(field->orig_index, result)); - method.BuildReturn(result, /*is_object=*/false); - method.Encode(); - }(readStaticField); - - // Set a static field - // void setStaticField() { TestClass.staticInteger = 7; } - MethodBuilder setStaticField{ - cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})}; - [&](MethodBuilder& method) { - const ir::FieldDecl* field = - dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int()); - LiveRegister number{method.AllocRegister()}; - method.BuildConst4(number, 7); - method.AddInstruction(Instruction::SetStaticField(field->orig_index, number)); - method.BuildReturn(); - method.Encode(); - }(setStaticField); - - // Read an instance field - // int readInstanceField(TestClass obj) { return obj.instanceField; } - MethodBuilder readInstanceField{ - cbuilder.CreateMethod("readInstanceField", Prototype{TypeDescriptor::Int(), test_class})}; - [&](MethodBuilder& method) { - const ir::FieldDecl* field = - dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int()); - LiveRegister result{method.AllocRegister()}; - method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0))); - method.BuildReturn(result, /*is_object=*/false); - method.Encode(); - }(readInstanceField); - - // Set an instance field - // void setInstanceField(TestClass obj) { obj.instanceField = 7; } - MethodBuilder setInstanceField{ - cbuilder.CreateMethod("setInstanceField", Prototype{TypeDescriptor::Void(), test_class})}; - [&](MethodBuilder& method) { - const ir::FieldDecl* field = - dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int()); - LiveRegister number{method.AllocRegister()}; - method.BuildConst4(number, 7); - method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number)); - method.BuildReturn(); - method.Encode(); - }(setInstanceField); - - slicer::MemView image{dex_file.CreateImage()}; - std::ofstream out_file(outdir + "/simple.dex"); - out_file.write(image.ptr<const char>(), image.size()); -} - -int main(int argc, char** argv) { - CHECK_EQ(argc, 2); - - string outdir = argv[1]; - - GenerateTrivialDexFile(outdir); - GenerateSimpleTestCases(outdir); -} diff --git a/startop/view_compiler/java_lang_builder.cc b/startop/view_compiler/java_lang_builder.cc deleted file mode 100644 index 920caeecf58e..000000000000 --- a/startop/view_compiler/java_lang_builder.cc +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2018 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 "java_lang_builder.h" - -#include "android-base/stringprintf.h" - -using android::base::StringPrintf; -using std::string; - -void JavaLangViewBuilder::Start() const { - out_ << StringPrintf("package %s;\n", package_.c_str()) - << "import android.content.Context;\n" - "import android.content.res.Resources;\n" - "import android.content.res.XmlResourceParser;\n" - "import android.util.AttributeSet;\n" - "import android.util.Xml;\n" - "import android.view.*;\n" - "import android.widget.*;\n" - "\n" - "public final class CompiledView {\n" - "\n" - "static <T extends View> T createView(Context context, AttributeSet attrs, View parent, " - "String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {" - "\n" - " if (factory2 != null) {\n" - " return (T)factory2.onCreateView(parent, name, context, attrs);\n" - " } else if (factory != null) {\n" - " return (T)factory.onCreateView(name, context, attrs);\n" - " }\n" - // TODO: find a way to call the private factory - " return null;\n" - "}\n" - "\n" - " public static View inflate(Context context) {\n" - " try {\n" - " LayoutInflater inflater = LayoutInflater.from(context);\n" - " LayoutInflater.Factory factory = inflater.getFactory();\n" - " LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n" - " Resources res = context.getResources();\n" - << StringPrintf(" XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n", - package_.c_str(), - layout_name_.c_str()) - << " AttributeSet attrs = Xml.asAttributeSet(xml);\n" - // The Java-language XmlPullParser needs a call to next to find the start document tag. - " xml.next(); // start document\n"; -} - -void JavaLangViewBuilder::Finish() const { - out_ << " } catch (Exception e) {\n" - " return null;\n" - " }\n" // end try - " }\n" // end inflate - "}\n"; // end CompiledView -} - -void JavaLangViewBuilder::StartView(const string& class_name, bool /*is_viewgroup*/) { - const string view_var = MakeVar("view"); - const string layout_var = MakeVar("layout"); - std::string parent = "null"; - if (!view_stack_.empty()) { - const StackEntry& parent_entry = view_stack_.back(); - parent = parent_entry.view_var; - } - out_ << " xml.next(); // <" << class_name << ">\n" - << StringPrintf(" %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n", - class_name.c_str(), - view_var.c_str(), - parent.c_str(), - class_name.c_str()) - << StringPrintf(" if (%s == null) %s = new %s(context, attrs);\n", - view_var.c_str(), - view_var.c_str(), - class_name.c_str()); - if (!view_stack_.empty()) { - out_ << StringPrintf(" ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n", - layout_var.c_str(), - parent.c_str()); - } - view_stack_.push_back({class_name, view_var, layout_var}); -} - -void JavaLangViewBuilder::FinishView() { - const StackEntry var = view_stack_.back(); - view_stack_.pop_back(); - if (!view_stack_.empty()) { - const string& parent = view_stack_.back().view_var; - out_ << StringPrintf(" xml.next(); // </%s>\n", var.class_name.c_str()) - << StringPrintf(" %s.addView(%s, %s);\n", - parent.c_str(), - var.view_var.c_str(), - var.layout_params_var.c_str()); - } else { - out_ << StringPrintf(" return %s;\n", var.view_var.c_str()); - } -} - -const std::string JavaLangViewBuilder::MakeVar(std::string prefix) { - std::stringstream v; - v << prefix << view_id_++; - return v.str(); -} diff --git a/startop/view_compiler/java_lang_builder.h b/startop/view_compiler/java_lang_builder.h deleted file mode 100644 index 69356d3a6594..000000000000 --- a/startop/view_compiler/java_lang_builder.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#ifndef JAVA_LANG_BUILDER_H_ -#define JAVA_LANG_BUILDER_H_ - -#include <iostream> -#include <sstream> -#include <vector> - -// Build Java language code to instantiate views. -// -// This has a very small interface to make it easier to generate additional -// backends, such as a direct-to-DEX version. -class JavaLangViewBuilder { - public: - JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout) - : package_(package), layout_name_(layout_name), out_(out) {} - - // Begin generating a class. Adds the package boilerplate, etc. - void Start() const; - // Finish generating a class, closing off any open curly braces, etc. - void Finish() const; - - // Begin creating a view (i.e. process the opening tag) - void StartView(const std::string& class_name, bool is_viewgroup); - // Finish a view, after all of its child nodes have been processed. - void FinishView(); - - private: - const std::string MakeVar(std::string prefix); - - std::string const package_; - std::string const layout_name_; - - std::ostream& out_; - - size_t view_id_ = 0; - - struct StackEntry { - // The class name for this view object - const std::string class_name; - - // The variable name that is holding the view object - const std::string view_var; - - // The variable name that holds the object's layout parameters - const std::string layout_params_var; - }; - std::vector<StackEntry> view_stack_; -}; - -#endif // JAVA_LANG_BUILDER_H_ diff --git a/startop/view_compiler/layout_validation.cc b/startop/view_compiler/layout_validation.cc deleted file mode 100644 index 8c7737749124..000000000000 --- a/startop/view_compiler/layout_validation.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2018 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 "layout_validation.h" - -#include "android-base/stringprintf.h" - -namespace startop { - -void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) { - if (0 == name.compare(u"merge")) { - message_ = "Merge tags are not supported"; - can_compile_ = false; - } - if (0 == name.compare(u"include")) { - message_ = "Include tags are not supported"; - can_compile_ = false; - } - if (0 == name.compare(u"view")) { - message_ = "View tags are not supported"; - can_compile_ = false; - } - if (0 == name.compare(u"fragment")) { - message_ = "Fragment tags are not supported"; - can_compile_ = false; - } -} - -} // namespace startop
\ No newline at end of file diff --git a/startop/view_compiler/layout_validation.h b/startop/view_compiler/layout_validation.h deleted file mode 100644 index bed34bb38e5e..000000000000 --- a/startop/view_compiler/layout_validation.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#ifndef LAYOUT_VALIDATION_H_ -#define LAYOUT_VALIDATION_H_ - -#include "dex_builder.h" - -#include <string> - -namespace startop { - -// This visitor determines whether a layout can be compiled. Since we do not currently support all -// features, such as includes and merges, we need to pre-validate the layout before we start -// compiling. -class LayoutValidationVisitor { - public: - void VisitStartDocument() const {} - void VisitEndDocument() const {} - void VisitStartTag(const std::u16string& name); - void VisitEndTag() const {} - - const std::string& message() const { return message_; } - bool can_compile() const { return can_compile_; } - - private: - std::string message_{"Okay"}; - bool can_compile_{true}; -}; - -} // namespace startop - -#endif // LAYOUT_VALIDATION_H_ diff --git a/startop/view_compiler/layout_validation_test.cc b/startop/view_compiler/layout_validation_test.cc deleted file mode 100644 index b74cdae8d725..000000000000 --- a/startop/view_compiler/layout_validation_test.cc +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2018 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 "tinyxml_layout_parser.h" - -#include "gtest/gtest.h" - -using startop::CanCompileLayout; -using std::string; - -namespace { -void ValidateXmlText(const string& xml, bool expected) { - tinyxml2::XMLDocument doc; - doc.Parse(xml.c_str()); - EXPECT_EQ(CanCompileLayout(doc), expected); -} -} // namespace - -TEST(LayoutValidationTest, SingleButtonLayout) { - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<Button xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="Hello, World!"> - -</Button>)"; - ValidateXmlText(xml, /*expected=*/true); -} - -TEST(LayoutValidationTest, SmallConstraintLayout) { - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<android.support.constraint.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <Button - android:id="@+id/button6" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:layout_marginBottom="16dp" - android:text="Button" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - - <Button - android:id="@+id/button7" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:layout_marginBottom="16dp" - android:text="Button2" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/button6" /> - - <Button - android:id="@+id/button8" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:layout_marginBottom="16dp" - android:text="Button1" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/button7" /> -</android.support.constraint.ConstraintLayout>)"; - ValidateXmlText(xml, /*expected=*/true); -} - -TEST(LayoutValidationTest, MergeNode) { - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - - <TextView - android:id="@+id/textView3" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="TextView" /> - - <Button - android:id="@+id/button9" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Button" /> -</merge>)"; - ValidateXmlText(xml, /*expected=*/false); -} - -TEST(LayoutValidationTest, IncludeLayout) { - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<android.support.constraint.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <include - layout="@layout/single_button_layout" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> -</android.support.constraint.ConstraintLayout>)"; - ValidateXmlText(xml, /*expected=*/false); -} - -TEST(LayoutValidationTest, ViewNode) { - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<android.support.constraint.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <view - class="android.support.design.button.MaterialButton" - id="@+id/view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> -</android.support.constraint.ConstraintLayout>)"; - ValidateXmlText(xml, /*expected=*/false); -} - -TEST(LayoutValidationTest, FragmentNode) { - // This test case is from https://developer.android.com/guide/components/fragments - const string xml = R"(<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <fragment android:name="com.example.news.ArticleListFragment" - android:id="@+id/list" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="match_parent" /> - <fragment android:name="com.example.news.ArticleReaderFragment" - android:id="@+id/viewer" - android:layout_weight="2" - android:layout_width="0dp" - android:layout_height="match_parent" /> -</LinearLayout>)"; - ValidateXmlText(xml, /*expected=*/false); -} diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc deleted file mode 100644 index 11ecde27f5cd..000000000000 --- a/startop/view_compiler/main.cc +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2018 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 "gflags/gflags.h" - -#include "android-base/stringprintf.h" -#include "apk_layout_compiler.h" -#include "dex_builder.h" -#include "dex_layout_compiler.h" -#include "java_lang_builder.h" -#include "layout_validation.h" -#include "tinyxml_layout_parser.h" -#include "util.h" - -#include "tinyxml2.h" - -#include <fstream> -#include <iostream> -#include <sstream> -#include <string> -#include <vector> - -namespace { - -using namespace tinyxml2; -using android::base::StringPrintf; -using startop::dex::ClassBuilder; -using startop::dex::DexBuilder; -using startop::dex::MethodBuilder; -using startop::dex::Prototype; -using startop::dex::TypeDescriptor; -using namespace startop::util; -using std::string; - -constexpr char kStdoutFilename[]{"stdout"}; - -DEFINE_bool(apk, false, "Compile layouts in an APK"); -DEFINE_bool(dex, false, "Generate a DEX file instead of Java"); -DEFINE_int32(infd, -1, "Read input from the given file descriptor"); -DEFINE_string(out, kStdoutFilename, "Where to write the generated class"); -DEFINE_string(package, "", "The package name for the generated class (required)"); - -template <typename Visitor> -class XmlVisitorAdapter : public XMLVisitor { - public: - explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {} - - bool VisitEnter(const XMLDocument& /*doc*/) override { - visitor_->VisitStartDocument(); - return true; - } - - bool VisitExit(const XMLDocument& /*doc*/) override { - visitor_->VisitEndDocument(); - return true; - } - - bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override { - visitor_->VisitStartTag( - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes( - element.Name())); - return true; - } - - bool VisitExit(const XMLElement& /*element*/) override { - visitor_->VisitEndTag(); - return true; - } - - private: - Visitor* visitor_; -}; - -template <typename Builder> -void CompileLayout(XMLDocument* xml, Builder* builder) { - startop::LayoutCompilerVisitor visitor{builder}; - XmlVisitorAdapter<decltype(visitor)> adapter{&visitor}; - xml->Accept(&adapter); -} - -} // end namespace - -int main(int argc, char** argv) { - constexpr size_t kProgramName = 0; - constexpr size_t kFileNameParam = 1; - constexpr size_t kNumRequiredArgs = 1; - - gflags::SetUsageMessage( - "Compile XML layout files into equivalent Java language code\n" - "\n" - " example usage: viewcompiler layout.xml --package com.example.androidapp"); - gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true); - - gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package"); - if (argc < kNumRequiredArgs || cmd.is_default) { - gflags::ShowUsageWithFlags(argv[kProgramName]); - return 1; - } - - const bool is_stdout = FLAGS_out == kStdoutFilename; - - std::ofstream outfile; - if (!is_stdout) { - outfile.open(FLAGS_out); - } - - if (FLAGS_apk) { - const startop::CompilationTarget target = - FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage; - if (FLAGS_infd >= 0) { - startop::CompileApkLayoutsFd( - android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile); - } else { - if (argc < 2) { - gflags::ShowUsageWithFlags(argv[kProgramName]); - return 1; - } - const char* const filename = argv[kFileNameParam]; - startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile); - } - return 0; - } - - const char* const filename = argv[kFileNameParam]; - const string layout_name = startop::util::FindLayoutNameFromFilename(filename); - - XMLDocument xml; - xml.LoadFile(filename); - - string message{}; - if (!startop::CanCompileLayout(xml, &message)) { - LOG(ERROR) << "Layout not supported: " << message; - return 1; - } - - if (FLAGS_dex) { - DexBuilder dex_file; - string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str()); - ClassBuilder compiled_view{dex_file.MakeClass(class_name)}; - MethodBuilder method{compiled_view.CreateMethod( - layout_name, - Prototype{TypeDescriptor::FromClassname("android.view.View"), - TypeDescriptor::FromClassname("android.content.Context"), - TypeDescriptor::Int()})}; - startop::DexViewBuilder builder{&method}; - CompileLayout(&xml, &builder); - method.Encode(); - - slicer::MemView image{dex_file.CreateImage()}; - - (is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size()); - } else { - // Generate Java language output. - JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile}; - - CompileLayout(&xml, &builder); - } - return 0; -} diff --git a/startop/view_compiler/tinyxml_layout_parser.h b/startop/view_compiler/tinyxml_layout_parser.h deleted file mode 100644 index 8f714a2c5a3f..000000000000 --- a/startop/view_compiler/tinyxml_layout_parser.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#ifndef TINYXML_LAYOUT_PARSER_H_ -#define TINYXML_LAYOUT_PARSER_H_ - -#include "tinyxml2.h" - -#include <codecvt> -#include <locale> -#include <string> - -namespace startop { - -template <typename Visitor> -class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor { - public: - explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {} - - bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override { - visitor_->VisitStartDocument(); - return true; - } - - bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override { - visitor_->VisitEndDocument(); - return true; - } - - bool VisitEnter(const tinyxml2::XMLElement& element, - const tinyxml2::XMLAttribute* /*firstAttribute*/) override { - visitor_->VisitStartTag( - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes( - element.Name())); - return true; - } - - bool VisitExit(const tinyxml2::XMLElement& /*element*/) override { - visitor_->VisitEndTag(); - return true; - } - - private: - Visitor* visitor_; -}; - -// Returns whether a layout resource represented by a TinyXML document is supported by the layout -// compiler. -bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr); - -} // namespace startop - -#endif // TINYXML_LAYOUT_PARSER_H_ diff --git a/startop/view_compiler/util.cc b/startop/view_compiler/util.cc deleted file mode 100644 index c34d7b059cfc..000000000000 --- a/startop/view_compiler/util.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2018 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 "util.h" - -using std::string; - -namespace startop { -namespace util { - -// TODO: see if we can borrow this from somewhere else, like aapt2. -string FindLayoutNameFromFilename(const string& filename) { - size_t start = filename.rfind('/'); - if (start == string::npos) { - start = 0; - } else { - start++; // advance past '/' character - } - size_t end = filename.find('.', start); - - return filename.substr(start, end - start); -} - -} // namespace util -} // namespace startop diff --git a/startop/view_compiler/util_test.cc b/startop/view_compiler/util_test.cc deleted file mode 100644 index 50682a04e3b1..000000000000 --- a/startop/view_compiler/util_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 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 "util.h" - -#include "gtest/gtest.h" - -using std::string; - -namespace startop { -namespace util { - -TEST(UtilTest, FindLayoutNameFromFilename) { - EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("foo/bar.xml")); - EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("bar.xml")); - EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("./foo/bar.xml")); - EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("/foo/bar.xml")); -} - -} // namespace util -} // namespace startop diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 038b93fc2a43..042b2a3de0a1 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3148,6 +3148,14 @@ public class CarrierConfigManager { public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array"; /** + * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming. + * The roaming indicator will be shown if this is {@code true} and will not be shown if this is + * {@code false}. + */ + @FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON) + public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; + + /** * URL from which the proto containing the public key of the Carrier used for * IMSI encryption will be downloaded. * @hide @@ -3313,11 +3321,11 @@ public class CarrierConfigManager { * If {@code false} the SPN display checks if the current MCC/MNC is different from the * SIM card's MCC/MNC. * - * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY - * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY - * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_FORCE_HOME_NETWORK_BOOL + * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_FORCE_HOME_NETWORK_BOOL * * @hide */ @@ -8798,7 +8806,9 @@ public class CarrierConfigManager { * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_NONE}, * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1024_BIT_MODP}, * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1536_BIT_MODP}, - * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP} + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP}, + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_3072_BIT_MODP}, + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_4096_BIT_MODP} */ public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = KEY_PREFIX + "diffie_hellman_groups_int_array"; @@ -8866,7 +8876,8 @@ public class CarrierConfigManager { /** * List of supported encryption algorithms for child session. Possible values are - * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC} + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} */ public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array"; @@ -10193,6 +10204,7 @@ public class CarrierConfigManager { false); sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index cee2efbbedff..f161f319b0c2 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -43,6 +44,7 @@ import com.android.i18n.phonenumbers.NumberParseException; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -2957,7 +2959,8 @@ public class PhoneNumberUtils { * @param number The phone number used for WPS call. * @return {@code true} if number matches WPS pattern and {@code false} otherwise. */ - public static boolean isWpsCallNumber(@Nullable String number) { + @FlaggedApi(Flags.FLAG_ENABLE_WPS_CHECK_API_FLAG) + public static boolean isWpsCallNumber(@NonNull String number) { return (number != null) && (number.startsWith(PREFIX_WPS) || number.startsWith(PREFIX_WPS_CLIR_ACTIVATE) || number.startsWith(PREFIX_WPS_CLIR_DEACTIVATE)); diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 6b47db1e2251..e2cd4f8da2c4 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -469,6 +469,9 @@ oneway interface ISatellite { * The satellite service should report the NTN signal strength via * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes. * + * Note: This API can be invoked multiple times. If the modem is already in the expected + * state from a previous request, subsequent invocations may be ignored. + * * @param resultCallback The callback to receive the error code result of the operation. * * Valid result codes returned: @@ -485,6 +488,9 @@ oneway interface ISatellite { * be called when device is screen off to save power by not letting signal strength updates to * wake up application processor. * + * Note: This API can be invoked multiple times. If the modem is already in the expected + * state from a previous request, subsequent invocations may be ignored. + * * @param resultCallback The callback to receive the error code result of the operation. * * Valid result codes returned: diff --git a/tests/notification/Android.bp b/tests/notification/Android.bp deleted file mode 100644 index 1c1b5a231e86..000000000000 --- a/tests/notification/Android.bp +++ /dev/null @@ -1,16 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "NotificationTests", - // Include all test java files. - srcs: ["src/**/*.java"], - libs: ["android.test.runner.stubs"], - sdk_version: "21", -} diff --git a/tests/notification/AndroidManifest.xml b/tests/notification/AndroidManifest.xml deleted file mode 100644 index 7cee00a72781..000000000000 --- a/tests/notification/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.tests.notification" - > - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation - android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.frameworks.tests.notification" - android:label="Frameworks Notification Tests" /> -</manifest> diff --git a/tests/notification/OWNERS b/tests/notification/OWNERS deleted file mode 100644 index 396fd1213aca..000000000000 --- a/tests/notification/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/notification/OWNERS diff --git a/tests/notification/res/drawable-nodpi/arubin_hed.jpeg b/tests/notification/res/drawable-nodpi/arubin_hed.jpeg Binary files differdeleted file mode 100644 index c6d8ae96485b..000000000000 --- a/tests/notification/res/drawable-nodpi/arubin_hed.jpeg +++ /dev/null diff --git a/tests/notification/res/drawable-nodpi/bucket.png b/tests/notification/res/drawable-nodpi/bucket.png Binary files differdeleted file mode 100644 index c8656493dec9..000000000000 --- a/tests/notification/res/drawable-nodpi/bucket.png +++ /dev/null diff --git a/tests/notification/res/drawable-nodpi/matias_hed.jpg b/tests/notification/res/drawable-nodpi/matias_hed.jpg Binary files differdeleted file mode 100644 index 8cc3081359f7..000000000000 --- a/tests/notification/res/drawable-nodpi/matias_hed.jpg +++ /dev/null diff --git a/tests/notification/res/drawable-nodpi/page_hed.jpg b/tests/notification/res/drawable-nodpi/page_hed.jpg Binary files differdeleted file mode 100644 index ea950c8bdb25..000000000000 --- a/tests/notification/res/drawable-nodpi/page_hed.jpg +++ /dev/null diff --git a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg b/tests/notification/res/drawable-nodpi/romainguy_hed.jpg Binary files differdeleted file mode 100644 index 5b7643eb9063..000000000000 --- a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg +++ /dev/null diff --git a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg b/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg Binary files differdeleted file mode 100644 index 68473ba6c962..000000000000 --- a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/add.png b/tests/notification/res/drawable-xhdpi/add.png Binary files differdeleted file mode 100644 index 7226b3d8b40c..000000000000 --- a/tests/notification/res/drawable-xhdpi/add.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png b/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png Binary files differdeleted file mode 100644 index ca20a917be3d..000000000000 --- a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/ic_end_call.png b/tests/notification/res/drawable-xhdpi/ic_end_call.png Binary files differdeleted file mode 100644 index c464a6dc5f6f..000000000000 --- a/tests/notification/res/drawable-xhdpi/ic_end_call.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/ic_media_next.png b/tests/notification/res/drawable-xhdpi/ic_media_next.png Binary files differdeleted file mode 100644 index 4def965cec24..000000000000 --- a/tests/notification/res/drawable-xhdpi/ic_media_next.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png b/tests/notification/res/drawable-xhdpi/ic_menu_upload.png Binary files differdeleted file mode 100644 index f1438ed66858..000000000000 --- a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/icon.png b/tests/notification/res/drawable-xhdpi/icon.png Binary files differdeleted file mode 100644 index 189e85b5d4f6..000000000000 --- a/tests/notification/res/drawable-xhdpi/icon.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png b/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png Binary files differdeleted file mode 100644 index 658d04feabf9..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png b/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png Binary files differdeleted file mode 100644 index 5ae7782d5d5e..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_email.png b/tests/notification/res/drawable-xhdpi/stat_notify_email.png Binary files differdeleted file mode 100644 index 23c4672e8d18..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_email.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png b/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png Binary files differdeleted file mode 100644 index 8719eff5ae1a..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png b/tests/notification/res/drawable-xhdpi/stat_notify_sms.png Binary files differdeleted file mode 100644 index 323cb3df46f9..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png Binary files differdeleted file mode 100644 index 26dcda358bf8..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png Binary files differdeleted file mode 100644 index b8b2f8ad1044..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png b/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png Binary files differdeleted file mode 100644 index 12cae9f110b9..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png +++ /dev/null diff --git a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png b/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png Binary files differdeleted file mode 100644 index db42b7cd969f..000000000000 --- a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png +++ /dev/null diff --git a/tests/notification/res/layout/full_screen.xml b/tests/notification/res/layout/full_screen.xml deleted file mode 100644 index 6ff75525d7a1..000000000000 --- a/tests/notification/res/layout/full_screen.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:layout_height="match_parent" - android:layout_width="match_parent" - android:src="@drawable/page_hed" - android:onClick="dismiss" - /> -</FrameLayout>
\ No newline at end of file diff --git a/tests/notification/res/layout/main.xml b/tests/notification/res/layout/main.xml deleted file mode 100644 index f5a740f50bd1..000000000000 --- a/tests/notification/res/layout/main.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - > - <LinearLayout android:id="@+id/linearLayout1" android:orientation="vertical" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_width="match_parent" android:layout_margin="35dp"> - <Button android:id="@+id/button1" android:text="@string/post_button_label" android:layout_height="wrap_content" android:layout_width="match_parent" android:onClick="doPost"></Button> - <Button android:id="@+id/button2" android:text="@string/remove_button_label" android:layout_height="wrap_content" android:layout_width="match_parent" android:onClick="doRemove"></Button> - </LinearLayout> -</FrameLayout> diff --git a/tests/notification/res/values/dimens.xml b/tests/notification/res/values/dimens.xml deleted file mode 100644 index 21e7bc3f1ef2..000000000000 --- a/tests/notification/res/values/dimens.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2012, 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. -*/ ---> -<resources> - <!-- The width of the big icons in notifications. --> - <dimen name="notification_large_icon_width">64dp</dimen> - <!-- The width of the big icons in notifications. --> - <dimen name="notification_large_icon_height">64dp</dimen> -</resources> diff --git a/tests/notification/res/values/strings.xml b/tests/notification/res/values/strings.xml deleted file mode 100644 index 80bf10326f7a..000000000000 --- a/tests/notification/res/values/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="hello">Hello World, NotificationShowcaseActivity!</string> - <string name="app_name">NotificationShowcase</string> - <string name="post_button_label">Post Notifications</string> - <string name="remove_button_label">Remove Notifications</string> - <string name="answered">call answered</string> - <string name="ignored">call ignored</string> - <string name="full_screen_name">Full Screen Activity</string> -</resources> diff --git a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java b/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java deleted file mode 100644 index 5d639f6f6266..000000000000 --- a/tests/notification/src/com/android/frameworks/tests/notification/NotificationTests.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Parcel; -import android.os.SystemClock; -import android.provider.ContactsContract; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.Suppress; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.style.StyleSpan; -import android.util.Log; -import android.view.View; -import android.widget.Toast; - -import java.lang.reflect.Method; -import java.lang.InterruptedException; -import java.lang.NoSuchMethodError; -import java.lang.NoSuchMethodException; -import java.util.ArrayList; - -import com.android.frameworks.tests.notification.R; - -public class NotificationTests extends AndroidTestCase { - private static final String TAG = "NOTEST"; - public static void L(String msg, Object... args) { - Log.v(TAG, (args == null || args.length == 0) ? msg : String.format(msg, args)); - } - - public static final String ACTION_CREATE = "create"; - public static final int NOTIFICATION_ID = 31338; - - public static final boolean SHOW_PHONE_CALL = false; - public static final boolean SHOW_INBOX = true; - public static final boolean SHOW_BIG_TEXT = true; - public static final boolean SHOW_BIG_PICTURE = true; - public static final boolean SHOW_MEDIA = true; - public static final boolean SHOW_STOPWATCH = false; - public static final boolean SHOW_SOCIAL = false; - public static final boolean SHOW_CALENDAR = false; - public static final boolean SHOW_PROGRESS = false; - - private static Bitmap getBitmap(Context context, int resId) { - int largeIconWidth = (int) context.getResources() - .getDimension(R.dimen.notification_large_icon_width); - int largeIconHeight = (int) context.getResources() - .getDimension(R.dimen.notification_large_icon_height); - Drawable d = context.getResources().getDrawable(resId); - Bitmap b = Bitmap.createBitmap(largeIconWidth, largeIconHeight, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - d.setBounds(0, 0, largeIconWidth, largeIconHeight); - d.draw(c); - return b; - } - - private static PendingIntent makeEmailIntent(Context context, String who) { - final Intent intent = new Intent(android.content.Intent.ACTION_SENDTO, - Uri.parse("mailto:" + who)); - return PendingIntent.getActivity( - context, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT); - } - - static final String[] LINES = new String[] { - "Uh oh", - "Getting kicked out of this room", - "I'll be back in 5-10 minutes.", - "And now \u2026 I have to find my shoes. \uD83D\uDC63", - "\uD83D\uDC5F \uD83D\uDC5F", - "\uD83D\uDC60 \uD83D\uDC60", - }; - static final int MAX_LINES = 5; - public static Notification makeBigTextNotification(Context context, int update, int id, - long when) { - String personUri = null; - /* - Cursor c = null; - try { - String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY }; - String selections = ContactsContract.Contacts.DISPLAY_NAME + " = 'Mike Cleron'"; - final ContentResolver contentResolver = context.getContentResolver(); - c = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, - projection, selections, null, null); - if (c != null && c.getCount() > 0) { - c.moveToFirst(); - int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); - int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID); - String lookupKey = c.getString(lookupIdx); - long contactId = c.getLong(idIdx); - Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey); - personUri = lookupUri.toString(); - } - } finally { - if (c != null) { - c.close(); - } - } - if (TextUtils.isEmpty(personUri)) { - Log.w(TAG, "failed to find contact for Mike Cleron"); - } else { - Log.w(TAG, "Mike Cleron is " + personUri); - } - */ - - StringBuilder longSmsText = new StringBuilder(); - int end = 2 + update; - if (end > LINES.length) { - end = LINES.length; - } - final int start = Math.max(0, end - MAX_LINES); - for (int i=start; i<end; i++) { - if (i >= LINES.length) break; - if (i > start) longSmsText.append("\n"); - longSmsText.append(LINES[i]); - } - if (update > 2) { - when = System.currentTimeMillis(); - } - Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle() - .bigText(longSmsText); - Notification bigText = new Notification.Builder(context) - .setContentTitle("Mike Cleron") - .setContentIntent(ToastService.getPendingIntent(context, "Clicked on bigText")) - .setContentText(longSmsText) - //.setTicker("Mike Cleron: " + longSmsText) - .setWhen(when) - .setLargeIcon(getBitmap(context, R.drawable.bucket)) - .setPriority(Notification.PRIORITY_HIGH) - .setNumber(update) - .setSmallIcon(R.drawable.stat_notify_talk_text) - .setStyle(bigTextStyle) - .setDefaults(Notification.DEFAULT_SOUND) - .addPerson(personUri) - .build(); - return bigText; - } - - public static Notification makeUploadNotification(Context context, int progress, long when) { - Notification.Builder uploadNotification = new Notification.Builder(context) - .setContentTitle("File Upload") - .setContentText("foo.txt") - .setPriority(Notification.PRIORITY_MIN) - .setContentIntent(ToastService.getPendingIntent(context, "Clicked on Upload")) - .setWhen(when) - .setSmallIcon(R.drawable.ic_menu_upload) - .setProgress(100, Math.min(progress, 100), false); - return uploadNotification.build(); - } - - static SpannableStringBuilder BOLD(CharSequence str) { - final SpannableStringBuilder ssb = new SpannableStringBuilder(str); - ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); - return ssb; - } - - public static class ToastService extends IntentService { - - private static final String TAG = "ToastService"; - - private static final String ACTION_TOAST = "toast"; - - private Handler handler; - - public ToastService() { - super(TAG); - } - public ToastService(String name) { - super(name); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - handler = new Handler(); - return super.onStartCommand(intent, flags, startId); - } - - @Override - protected void onHandleIntent(Intent intent) { - Log.v(TAG, "clicked a thing! intent=" + intent.toString()); - if (intent.hasExtra("text")) { - final String text = intent.getStringExtra("text"); - handler.post(new Runnable() { - @Override - public void run() { - Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show(); - Log.v(TAG, "toast " + text); - } - }); - } - } - - public static PendingIntent getPendingIntent(Context context, String text) { - Intent toastIntent = new Intent(context, ToastService.class); - toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message - toastIntent.putExtra("text", text); - PendingIntent pi = PendingIntent.getService( - context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); - return pi; - } - } - - public static void sleepIfYouCan(int ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException e) {} - } - - @Override - public void setUp() throws Exception { - super.setUp(); - } - - public static String summarize(Notification n) { - return String.format("<notif title=\"%s\" icon=0x%08x view=%s>", - n.extras.get(Notification.EXTRA_TITLE), - n.icon, - String.valueOf(n.contentView)); - } - - public void testCreate() throws Exception { - ArrayList<Notification> mNotifications = new ArrayList<Notification>(); - NotificationManager noMa = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - L("Constructing notifications..."); - if (SHOW_BIG_TEXT) { - int bigtextId = mNotifications.size(); - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = makeBigTextNotification(mContext, 0, bigtextId, System.currentTimeMillis()); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - int uploadId = mNotifications.size(); - long uploadWhen = System.currentTimeMillis(); - - if (SHOW_PROGRESS) { - mNotifications.add(makeUploadNotification(mContext, 0, uploadWhen)); - } - - if (SHOW_PHONE_CALL) { - int phoneId = mNotifications.size(); - final PendingIntent fullscreenIntent - = FullScreenActivity.getPendingIntent(mContext, phoneId); - final long time = SystemClock.currentThreadTimeMillis(); - Notification phoneCall = new Notification.Builder(mContext) - .setContentTitle("Incoming call") - .setContentText("Matias Duarte") - .setLargeIcon(getBitmap(mContext, R.drawable.matias_hed)) - .setSmallIcon(R.drawable.stat_sys_phone_call) - .setDefaults(Notification.DEFAULT_SOUND) - .setPriority(Notification.PRIORITY_MAX) - .setContentIntent(fullscreenIntent) - .setFullScreenIntent(fullscreenIntent, true) - .addAction(R.drawable.ic_dial_action_call, "Answer", - ToastService.getPendingIntent(mContext, "Clicked on Answer")) - .addAction(R.drawable.ic_end_call, "Ignore", - ToastService.getPendingIntent(mContext, "Clicked on Ignore")) - .setOngoing(true) - .addPerson(Uri.fromParts("tel", "1 (617) 555-1212", null).toString()) - .build(); - L(" %s: create=%dms", phoneCall.toString(), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(phoneCall); - } - - if (SHOW_STOPWATCH) { - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = new Notification.Builder(mContext) - .setContentTitle("Stopwatch PRO") - .setContentText("Counting up") - .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Stopwatch")) - .setSmallIcon(R.drawable.stat_notify_alarm) - .setUsesChronometer(true) - .build(); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - if (SHOW_CALENDAR) { - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = new Notification.Builder(mContext) - .setContentTitle("J Planning") - .setContentText("The Botcave") - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.stat_notify_calendar) - .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on calendar event")) - .setContentInfo("7PM") - .addAction(R.drawable.stat_notify_snooze, "+10 min", - ToastService.getPendingIntent(mContext, "snoozed 10 min")) - .addAction(R.drawable.stat_notify_snooze_longer, "+1 hour", - ToastService.getPendingIntent(mContext, "snoozed 1 hr")) - .addAction(R.drawable.stat_notify_email, "Email", - ToastService.getPendingIntent(mContext, - "Congratulations, you just destroyed someone's inbox zero")) - .build(); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - if (SHOW_BIG_PICTURE) { - BitmapDrawable d = - (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.romainguy_rockaway); - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = new Notification.Builder(mContext) - .setContentTitle("Romain Guy") - .setContentText("I was lucky to find a Canon 5D Mk III at a local Bay Area " - + "store last week but I had not been able to try it in the field " - + "until tonight. After a few days of rain the sky finally cleared " - + "up. Rockaway Beach did not disappoint and I was finally able to " - + "see what my new camera feels like when shooting landscapes.") - .setSmallIcon(android.R.drawable.stat_notify_chat) - .setContentIntent( - ToastService.getPendingIntent(mContext, "Clicked picture")) - .setLargeIcon(getBitmap(mContext, R.drawable.romainguy_hed)) - .addAction(R.drawable.add, "Add to Gallery", - ToastService.getPendingIntent(mContext, "Added")) - .setStyle(new Notification.BigPictureStyle() - .bigPicture(d.getBitmap())) - .build(); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - if (SHOW_INBOX) { - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = new Notification.Builder(mContext) - .setContentTitle("New mail") - .setContentText("3 new messages") - .setSubText("example@gmail.com") - .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Mail")) - .setSmallIcon(R.drawable.stat_notify_email) - .setStyle(new Notification.InboxStyle() - .setSummaryText("example@gmail.com") - .addLine(BOLD("Alice:").append(" hey there!")) - .addLine(BOLD("Bob:").append(" hi there!")) - .addLine(BOLD("Charlie:").append(" Iz IN UR EMAILZ!!")) - ).build(); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - if (SHOW_SOCIAL) { - final long time = SystemClock.currentThreadTimeMillis(); - final Notification n = new Notification.Builder(mContext) - .setContentTitle("Social Network") - .setContentText("You were mentioned in a post") - .setContentInfo("example@gmail.com") - .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Social")) - .setSmallIcon(android.R.drawable.stat_notify_chat) - .setPriority(Notification.PRIORITY_LOW) - .build(); - L(" %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time); - mNotifications.add(n); - } - - L("Posting notifications..."); - for (int i=0; i<mNotifications.size(); i++) { - final int count = 4; - for (int j=0; j<count; j++) { - long time = SystemClock.currentThreadTimeMillis(); - final Notification n = mNotifications.get(i); - noMa.notify(NOTIFICATION_ID + i, n); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: notify=%dms (%d/%d)", summarize(n), time, - j + 1, count); - sleepIfYouCan(150); - } - } - - sleepIfYouCan(1000); - - L("Canceling notifications..."); - for (int i=0; i<mNotifications.size(); i++) { - final Notification n = mNotifications.get(i); - long time = SystemClock.currentThreadTimeMillis(); - noMa.cancel(NOTIFICATION_ID + i); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: cancel=%dms", summarize(n), time); - } - - sleepIfYouCan(500); - - L("Parceling notifications..."); - // we want to be able to use this test on older OSes that do not have getOpenAshmemSize - Method getOpenAshmemSize = null; - try { - getOpenAshmemSize = Parcel.class.getMethod("getOpenAshmemSize"); - } catch (NoSuchMethodException ex) { - } - for (int i=0; i<mNotifications.size(); i++) { - Parcel p = Parcel.obtain(); - { - final Notification n = mNotifications.get(i); - long time = SystemClock.currentThreadTimeMillis(); - n.writeToParcel(p, 0); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: write parcel=%dms size=%d ashmem=%s", - summarize(n), time, p.dataPosition(), - (getOpenAshmemSize != null) - ? getOpenAshmemSize.invoke(p) - : "???"); - p.setDataPosition(0); - } - - long time = SystemClock.currentThreadTimeMillis(); - final Notification n2 = Notification.CREATOR.createFromParcel(p); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: parcel read=%dms", summarize(n2), time); - - time = SystemClock.currentThreadTimeMillis(); - noMa.notify(NOTIFICATION_ID + i, n2); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: notify=%dms", summarize(n2), time); - } - - sleepIfYouCan(500); - - L("Canceling notifications..."); - for (int i=0; i<mNotifications.size(); i++) { - long time = SystemClock.currentThreadTimeMillis(); - final Notification n = mNotifications.get(i); - noMa.cancel(NOTIFICATION_ID + i); - time = SystemClock.currentThreadTimeMillis() - time; - L(" %s: cancel=%dms", summarize(n), time); - } - - -// if (SHOW_PROGRESS) { -// ProgressService.startProgressUpdater(this, uploadId, uploadWhen, 0); -// } - } - - public static class FullScreenActivity extends Activity { - public static final String EXTRA_ID = "id"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.full_screen); - final Intent intent = getIntent(); - if (intent != null && intent.hasExtra(EXTRA_ID)) { - final int id = intent.getIntExtra(EXTRA_ID, -1); - if (id >= 0) { - NotificationManager noMa = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - noMa.cancel(NOTIFICATION_ID + id); - } - } - } - - public void dismiss(View v) { - finish(); - } - - public static PendingIntent getPendingIntent(Context context, int id) { - Intent fullScreenIntent = new Intent(context, FullScreenActivity.class); - fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - fullScreenIntent.putExtra(EXTRA_ID, id); - PendingIntent pi = PendingIntent.getActivity( - context, 22, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); - return pi; - } - } -} - |