Merge "[DeviceAware] Re-add proxy methods taking AttributionSource in AppOpsService.aidl." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c8046ab..e3cbd92 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -14,10 +14,14 @@
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}",
+ ":android.hardware.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+ ":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
@@ -142,6 +146,21 @@
aconfig_declarations: "com.android.text.flags-aconfig",
}
+// Location
+aconfig_declarations {
+ name: "android.location.flags-aconfig",
+ package: "android.location.flags",
+ srcs: [
+ "location/java/android/location/flags/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.location.flags-aconfig-java",
+ aconfig_declarations: "android.location.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// NFC
aconfig_declarations {
name: "android.nfc.flags-aconfig",
@@ -281,6 +300,19 @@
aconfig_declarations: "android.view.accessibility.flags-aconfig",
}
+// Hardware
+aconfig_declarations {
+ name: "android.hardware.flags-aconfig",
+ package: "android.hardware.flags",
+ srcs: ["core/java/android/hardware/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.hardware.flags-aconfig-java",
+ aconfig_declarations: "android.hardware.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Widget
aconfig_declarations {
name: "android.widget.flags-aconfig",
@@ -537,6 +569,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// VCN
+aconfig_declarations {
+ name: "android.net.vcn.flags-aconfig",
+ package: "android.net.vcn",
+ srcs: ["core/java/android/net/vcn/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.net.vcn.flags-aconfig-java",
+ aconfig_declarations: "android.net.vcn.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// DevicePolicy
aconfig_declarations {
name: "device_policy_aconfig_flags",
@@ -569,3 +614,16 @@
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 0c199a6..b5f7e99 100644
--- a/Android.bp
+++ b/Android.bp
@@ -323,7 +323,6 @@
":installd_aidl",
":libaudioclient_aidl",
":libbinder_aidl",
- ":libbluetooth-binder-aidl",
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
@@ -615,30 +614,6 @@
],
}
-// 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",
@@ -707,6 +682,27 @@
"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 MissingPermission",
+ "--hide RequiresPermission",
+ "--hide SdkConstant",
+ "--hide Todo",
+ "--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/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 015487d..b42f7bc 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -28,3 +28,6 @@
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+
+# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
+flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 3e835b8..eb5502b 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -3,6 +3,6 @@
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 5bf2eb9..6550f26 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 @@
@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 bbe1485..592aff8 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 @@
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 4357d4f..293088d 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 @@
case "-u":
case "--user":
- userId = Integer.parseInt(getNextArgRequired());
+ userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "-n":
@@ -199,6 +199,10 @@
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 f47766e..79653f0 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 @@
// 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 45f15db..63eaa63 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 @@
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 458ff35..1fb54d5 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 @@
/** 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 @@
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 @@
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 @@
@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 @@
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 e086bfe..5744bdf 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -72,7 +72,6 @@
"android-non-updatable-doc-stubs-defaults",
"module-classpath-stubs-defaults",
],
- args: metalava_framework_docs_args,
}
droidstubs {
@@ -81,8 +80,7 @@
"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 @@
"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 @@
"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 @@
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 {
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 @@
// 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 @@
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 @@
],
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 @@
"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 @@
"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 @@
"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 @@
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 @@
hdf: [
"android.whichdoc online",
],
- args: framework_docs_only_args +
- " -staticonly " +
- " -toroot / " +
- " -devsite " +
- " -ignoreJdLinks ",
+ flags: [
+ "-staticonly",
+ "-toroot /",
+ "-devsite",
+ "-ignoreJdLinks",
+ ],
}
droiddoc {
@@ -362,8 +381,9 @@
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 7e78185..d566552 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -36,7 +36,6 @@
"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 @@
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 @@
"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 @@
"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 @@
"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 @@
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 71f366a..e0153f7 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
# 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/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 020ca33..ee2af12 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -16,10 +16,11 @@
package com.android.commands.svc;
+import android.app.ActivityThread;
import android.content.Context;
-import android.nfc.INfcAdapter;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.os.Looper;
public class NfcCommand extends Svc.Command {
@@ -42,27 +43,26 @@
@Override
public void run(String[] args) {
- INfcAdapter adapter = INfcAdapter.Stub.asInterface(
- ServiceManager.getService(Context.NFC_SERVICE));
-
+ Looper.prepareMainLooper();
+ ActivityThread.initializeMainlineModules();
+ Context context = ActivityThread.systemMain().getSystemContext();
+ NfcManager nfcManager = context.getSystemService(NfcManager.class);
+ if (nfcManager == null) {
+ System.err.println("Got a null NfcManager, is the system running?");
+ return;
+ }
+ NfcAdapter adapter = nfcManager.getDefaultAdapter();
if (adapter == null) {
System.err.println("Got a null NfcAdapter, is the system running?");
return;
}
-
- try {
- if (args.length == 2 && "enable".equals(args[1])) {
- adapter.enable();
- return;
- } else if (args.length == 2 && "disable".equals(args[1])) {
- adapter.disable(true);
- return;
- }
- } catch (RemoteException e) {
- System.err.println("NFC operation failed: " + e);
+ if (args.length == 2 && "enable".equals(args[1])) {
+ adapter.enable();
+ return;
+ } else if (args.length == 2 && "disable".equals(args[1])) {
+ adapter.disable(true);
return;
}
-
System.err.println(longHelp());
}
diff --git a/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
new file mode 100644
index 0000000..d5a5d7b
--- /dev/null
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index 7d5417d..18001e8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -232,6 +232,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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";
@@ -13285,9 +13322,12 @@
public final class SigningInfo implements android.os.Parcelable {
ctor public SigningInfo();
+ ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
ctor public SigningInfo(android.content.pm.SigningInfo);
method public int describeContents();
method public android.content.pm.Signature[] getApkContentsSigners();
+ method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
+ method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
method public android.content.pm.Signature[] getSigningCertificateHistory();
method public boolean hasMultipleSigners();
method public boolean hasPastSigningCertificates();
@@ -16259,6 +16299,8 @@
public static class Paint.FontMetricsInt {
ctor public Paint.FontMetricsInt();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetricsInt);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetrics);
field public int ascent;
field public int bottom;
field public int descent;
@@ -17577,7 +17619,7 @@
ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily build();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
}
public final class FontStyle {
@@ -17674,12 +17716,14 @@
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
@@ -17765,18 +17809,18 @@
method public float getAdvance();
method public float getAscent();
method public float getDescent();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeBold(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeItalic(@IntRange(from=0) int);
method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
method public float getGlyphX(@IntRange(from=0) int);
method public float getGlyphY(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getItalicOverride(@IntRange(from=0) int);
method public float getOffsetX();
method public float getOffsetY();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getWeightOverride(@IntRange(from=0) int);
method @IntRange(from=0) public int glyphCount();
- field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f;
+ field @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public static final float NO_OVERRIDE = 1.4E-45f;
}
public class TextRunShaper {
@@ -18173,6 +18217,13 @@
field public static final int YCBCR_P010 = 54; // 0x36
}
+ @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean supportMixedColorSpaces();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public static final android.os.Parcelable.Creator<android.hardware.OverlayProperties> CREATOR;
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -23981,7 +24032,7 @@
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23990,7 +24041,7 @@
method public void transferTo(@NonNull android.media.MediaRoute2Info);
method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceUpdatedCallback(@NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
}
@@ -24011,11 +24062,6 @@
method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
- @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public abstract static class MediaRouter2.RouteListingPreferenceCallback {
- ctor @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public MediaRouter2.RouteListingPreferenceCallback();
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
- }
-
public class MediaRouter2.RoutingController {
method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
method @Nullable public android.os.Bundle getControlHints();
@@ -32400,7 +32446,7 @@
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";
@@ -33007,7 +33053,7 @@
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);
}
@@ -33409,7 +33455,9 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
method public boolean isAdminUser();
+ method @FlaggedApi("android.multiuser.support_communal_profile") public boolean isCommunalProfile();
method public boolean isDemoUser();
+ method @FlaggedApi("android.multiuser.support_communal_profile_nextgen") public boolean isForegroundUserAdmin();
method public static boolean isHeadlessSystemUserMode();
method public boolean isManagedProfile();
method public boolean isProfile();
@@ -33471,7 +33519,7 @@
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";
@@ -43123,6 +43171,7 @@
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";
@@ -44460,7 +44509,7 @@
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);
@@ -45479,7 +45528,6 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
@@ -46718,6 +46766,7 @@
method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
@@ -46906,6 +46955,7 @@
method public int getLineVisibleEnd(int);
method public float getLineWidth(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @IntRange(from=1) public final int getMaxLines();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public int getOffsetForHorizontal(int, float);
method public int getOffsetToLeftOf(int);
method public int getOffsetToRightOf(int);
@@ -46972,6 +47022,7 @@
method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float);
method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float);
method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
@@ -47243,6 +47294,7 @@
method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public android.text.StaticLayout.Builder setText(CharSequence);
method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
@@ -49852,6 +49904,7 @@
method public android.view.Display.Mode getMode();
method public String getName();
method @Deprecated public int getOrientation();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public android.hardware.OverlayProperties getOverlaySupport();
method @Deprecated public int getPixelFormat();
method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
method public long getPresentationDeadlineNanos();
@@ -50130,13 +50183,6 @@
field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
}
- @FlaggedApi("android.view.flags.scroll_feedback_api") public class HapticScrollFeedbackProvider implements android.view.ScrollFeedbackProvider {
- ctor public HapticScrollFeedbackProvider(@NonNull android.view.View);
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
- }
-
public class InflateException extends java.lang.RuntimeException {
ctor public InflateException();
ctor public InflateException(String, Throwable);
@@ -51303,9 +51349,10 @@
}
@FlaggedApi("android.view.flags.scroll_feedback_api") public interface ScrollFeedbackProvider {
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") @NonNull public static android.view.ScrollFeedbackProvider createProvider(@NonNull android.view.View);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollLimit(int, int, int, boolean);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollProgress(int, int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onSnapToItem(int, int, int);
}
public class SearchEvent {
@@ -52587,7 +52634,6 @@
method @Deprecated public static int getEdgeSlop();
method @Deprecated public static int getFadingEdgeLength();
method @Deprecated public static long getGlobalActionKeyTimeout();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public int getHapticScrollFeedbackTickInterval(int, int, int);
method public static int getJumpTapTimeout();
method public static int getKeyRepeatDelay();
method public static int getKeyRepeatTimeout();
@@ -52627,7 +52673,6 @@
method @Deprecated public static int getWindowTouchSlop();
method public static long getZoomControlsTimeout();
method public boolean hasPermanentMenuKey();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public boolean isHapticScrollFeedbackEnabled(int, int, int);
method public boolean shouldShowMenuShortcutsWhenKeyboardPresent();
}
@@ -54520,8 +54565,8 @@
public class AnimationUtils {
ctor public AnimationUtils();
method public static long currentAnimationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeMillis();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeNanos();
method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -59928,6 +59973,7 @@
method public int getMinHeight();
method public int getMinLines();
method public int getMinWidth();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
method public android.text.TextPaint getPaint();
@@ -60064,6 +60110,7 @@
method public void setMinHeight(int);
method public void setMinLines(int);
method public void setMinWidth(int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public final void setMovementMethod(android.text.method.MovementMethod);
method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener);
method public void setPaintFlags(int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2af3c34..5bbff0b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -120,7 +120,6 @@
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 ac61107..6062f79 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -315,6 +315,7 @@
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 @@
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 @@
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 @@
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";
@@ -3872,6 +3875,7 @@
method public void setInstallAsInstantApp(boolean);
method public void setInstallAsVirtualPreload();
method public void setRequestDowngrade(boolean);
+ method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
@@ -5583,7 +5587,8 @@
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 +5642,13 @@
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 +5663,14 @@
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 +5683,7 @@
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 +5707,9 @@
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 +5837,11 @@
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 +5857,7 @@
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 +5866,9 @@
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 +5876,9 @@
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 +5887,7 @@
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 +5898,7 @@
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 +12716,7 @@
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 +12732,8 @@
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 +12755,7 @@
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);
}
@@ -12744,7 +12776,7 @@
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
- public final class HotwordTrainingAudio implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingAudio implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioFormat getAudioFormat();
method @NonNull public int getAudioType();
@@ -12755,7 +12787,7 @@
field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
}
- public static final class HotwordTrainingAudio.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingAudio.Builder {
ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
method @NonNull public android.service.voice.HotwordTrainingAudio build();
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
@@ -12764,7 +12796,7 @@
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
}
- public final class HotwordTrainingData implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingData implements android.os.Parcelable {
method public int describeContents();
method public static int getMaxTrainingDataBytes();
method public int getTimeoutStage();
@@ -12773,7 +12805,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
}
- public static final class HotwordTrainingData.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingData.Builder {
ctor public HotwordTrainingData.Builder();
method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
method @NonNull public android.service.voice.HotwordTrainingData build();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 03a58be..83b6880 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -872,7 +872,7 @@
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();
}
@@ -2148,7 +2148,9 @@
}
public final class BugreportParams {
+ field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4
field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+ field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7
}
public class Build {
@@ -2287,7 +2289,7 @@
public final class PowerManager {
method public boolean areAutoPowerSaveModesEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
- method public boolean isBatterySaverSupported();
+ method @FlaggedApi("android.os.battery_saver_supported_check_api") public boolean isBatterySaverSupported();
field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
}
@@ -2355,6 +2357,7 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getAliveUsers();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
+ method @FlaggedApi("android.multiuser.support_communal_profile") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getCommunalProfile();
method public int getMainDisplayIdAssignedToUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
@@ -3047,6 +3050,7 @@
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 +3494,6 @@
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);
@@ -3622,7 +3622,7 @@
package android.view.animation {
public class AnimationUtils {
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long);
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static void lockAnimationClock(long, long);
method public static void unlockAnimationClock();
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 21ed098..fd308ce 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -48,6 +48,7 @@
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 @@
}
@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 37111e9..c8317c8 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -27,7 +27,15 @@
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 @@
}
};
+ /**
+ * 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 e2082f7..180725e 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -706,8 +706,12 @@
}
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/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 2d55403..545ba8e 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -26,6 +26,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
@@ -736,7 +737,7 @@
* @see #isKeyguardLocked()
*/
public boolean isDeviceLocked() {
- return isDeviceLocked(mContext.getUserId());
+ return isDeviceLocked(mContext.getUserId(), mContext.getDeviceId());
}
/**
@@ -746,8 +747,17 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean isDeviceLocked(int userId) {
+ return isDeviceLocked(userId, mContext.getDeviceId());
+ }
+
+ /**
+ * Per-user per-device version of {@link #isDeviceLocked()}.
+ *
+ * @hide
+ */
+ public boolean isDeviceLocked(@UserIdInt int userId, int deviceId) {
try {
- return mTrustManager.isDeviceLocked(userId, mContext.getAssociatedDisplayId());
+ return mTrustManager.isDeviceLocked(userId, deviceId);
} catch (RemoteException e) {
return false;
}
@@ -769,7 +779,7 @@
* @see #isKeyguardSecure()
*/
public boolean isDeviceSecure() {
- return isDeviceSecure(mContext.getUserId());
+ return isDeviceSecure(mContext.getUserId(), mContext.getDeviceId());
}
/**
@@ -779,8 +789,17 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isDeviceSecure(int userId) {
+ return isDeviceSecure(userId, mContext.getDeviceId());
+ }
+
+ /**
+ * Per-user per-device version of {@link #isDeviceSecure()}.
+ *
+ * @hide
+ */
+ public boolean isDeviceSecure(@UserIdInt int userId, int deviceId) {
try {
- return mTrustManager.isDeviceSecure(userId, mContext.getAssociatedDisplayId());
+ return mTrustManager.isDeviceSecure(userId, deviceId);
} catch (RemoteException e) {
return false;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 94e1292..bbc843b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -374,7 +374,7 @@
* that you take care of task management as described in the
* <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
* Stack</a> document. In particular, make sure to read the
- * <a href="{@docRoot}/training/notify-user/navigation">Start
+ * <a href="{@docRoot}training/notify-user/navigation">Start
* an Activity from a Notification</a> page for the correct ways to launch an application from a
* notification.
*/
@@ -5606,8 +5606,10 @@
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// 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);
+ pillColor = Colors.flattenAlpha(
+ getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
+ 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 +12835,10 @@
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 mTertiaryFixedDimAccentColor = COLOR_INVALID;
+ private int mOnTertiaryFixedAccentTextColor = COLOR_INVALID;
+
private int mErrorColor = COLOR_INVALID;
private int mContrastColor = COLOR_INVALID;
private int mRippleAlpha = 0x33;
@@ -12891,7 +12896,7 @@
if (isColorized) {
if (rawColor == COLOR_DEFAULT) {
- int[] attrs = {R.attr.colorAccentSecondary};
+ int[] attrs = {R.attr.materialColorSecondary};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, Color.WHITE);
}
@@ -12908,18 +12913,22 @@
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
- mOnAccentTextColor = mBackgroundColor;
+ mOnTertiaryAccentTextColor = mBackgroundColor;
+ mTertiaryFixedDimAccentColor = mTertiaryAccentColor;
+ mOnTertiaryFixedAccentTextColor = mOnTertiaryAccentTextColor;
mErrorColor = mPrimaryTextColor;
mRippleAlpha = 0x33;
} else {
int[] attrs = {
- R.attr.colorSurface,
- R.attr.textColorPrimary,
- R.attr.textColorSecondary,
- R.attr.colorAccent,
- R.attr.colorAccentSecondary,
- R.attr.colorAccentTertiary,
- R.attr.textColorOnAccent,
+ R.attr.materialColorSurfaceContainerHigh,
+ R.attr.materialColorOnSurface,
+ R.attr.materialColorOnSurfaceVariant,
+ R.attr.materialColorPrimary,
+ R.attr.materialColorSecondary,
+ R.attr.materialColorTertiary,
+ R.attr.materialColorOnTertiary,
+ R.attr.materialColorTertiaryFixedDim,
+ R.attr.materialColorOnTertiaryFixed,
R.attr.colorError,
R.attr.colorControlHighlight
};
@@ -12930,9 +12939,11 @@
mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
- mErrorColor = getColor(ta, 7, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
+ mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+ mTertiaryFixedDimAccentColor = getColor(ta, 7, COLOR_INVALID);
+ mOnTertiaryFixedAccentTextColor = getColor(ta, 8, COLOR_INVALID);
+ mErrorColor = getColor(ta, 9, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 10, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12955,11 +12966,19 @@
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);
}
+ if (mTertiaryFixedDimAccentColor == COLOR_INVALID) {
+ mTertiaryFixedDimAccentColor = mContrastColor;
+ }
+ if (mOnTertiaryFixedAccentTextColor == COLOR_INVALID) {
+ mOnTertiaryFixedAccentTextColor = ColorUtils.setAlphaComponent(
+ ContrastColorUtil.resolvePrimaryColor(
+ ctx, mTertiaryFixedDimAccentColor, nightMode), 0xFF);
+ }
if (mErrorColor == COLOR_INVALID) {
mErrorColor = mPrimaryTextColor;
}
@@ -13029,8 +13048,18 @@
}
/** @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;
+ }
+
+ /** @return the theme's tertiary fixed dim accent color for colored UI elements. */
+ public @ColorInt int getTertiaryFixedDimAccentColor() {
+ return mTertiaryFixedDimAccentColor;
+ }
+
+ /** @return the theme's text color to be used on the tertiary fixed accent color. */
+ public @ColorInt int getOnTertiaryFixedAccentTextColor() {
+ return mOnTertiaryFixedAccentTextColor;
}
/**
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 113a6dd..7320cea 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -51,7 +51,7 @@
/**
* Determines when over-the-air system updates are installed on a device. Only a device policy
* controller (DPC) running in device owner mode or in profile owner mode for an organization-owned
- * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method
+ * device can set an update policy for the device by calling the {@code DevicePolicyManager} method
* {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
* policy affects the pending system update (if there is one) and any future updates for the device.
*
diff --git a/core/java/android/app/admin/flags/FlagUtils.java b/core/java/android/app/admin/flags/FlagUtils.java
deleted file mode 100644
index 7c3c3d5..0000000
--- a/core/java/android/app/admin/flags/FlagUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.admin.flags;
-
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
-
-import android.os.Binder;
-
-/**
- *
- * @hide
- */
-public final class FlagUtils {
- private FlagUtils() {}
-
- public static boolean isPolicyEngineMigrationV2Enabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return policyEngineMigrationV2Enabled();
- });
- }
-
- public static boolean isDevicePolicySizeTrackingEnabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return devicePolicySizeTrackingEnabled();
- });
- }
-
- public static boolean isOnboardingBugreportV2Enabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return onboardingBugreportV2Enabled();
- });
- }
-}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index f99615f..5a41c65 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -27,3 +27,17 @@
description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
bug: "263464464"
}
+
+flag {
+ name: "dedicated_device_control_enabled"
+ namespace: "enterprise"
+ description: "Allow the device management role holder to control which platform features are available on dedicated devices."
+ bug: "281964214"
+}
+
+flag {
+ name: "security_log_v2_enabled"
+ namespace: "enterprise"
+ description: "Improve access to security logging in the context of Zero Trust."
+ bug: "295324350"
+}
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index cf54c2a..5f8de77 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,7 +1,4 @@
# Bug component: 643919
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
+hackz@google.com
+volnov@google.com
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index b34f678..06bff5d 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,6 +58,11 @@
super(in);
}
+ @Override
+ boolean isActivityLifecycleItem() {
+ return true;
+ }
+
/** A final lifecycle state that an activity should reach. */
@LifecycleState
public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 8617386..9c0cd39 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,6 +45,13 @@
*/
public class ClientTransaction implements Parcelable, ObjectPoolItem {
+ /**
+ * List of transaction items that should be executed in order. Including both
+ * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
+ */
+ @Nullable
+ private List<ClientTransactionItem> mTransactionItems;
+
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;
@@ -64,9 +71,32 @@
}
/**
- * Add a message to the end of the sequence of callbacks.
- * @param activityCallback A single message that can contain a lifecycle request/callback.
+ * Adds a message to the end of the sequence of transaction items.
+ * @param item A single message that can contain a client activity/window request/callback.
+ * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
*/
+ public void addTransactionItem(@NonNull ClientTransactionItem item) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
+ }
+
+ /**
+ * Gets the list of client window requests/callbacks.
+ * TODO(b/260873529): must be non null after remove the deprecated methods.
+ */
+ @Nullable
+ public List<ClientTransactionItem> getTransactionItems() {
+ return mTransactionItems;
+ }
+
+ /**
+ * Adds a message to the end of the sequence of callbacks.
+ * @param activityCallback A single message that can contain a lifecycle request/callback.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
+ */
+ @Deprecated
public void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
@@ -74,25 +104,35 @@
mActivityCallbacks.add(activityCallback);
}
- /** Get the list of callbacks. */
+ /**
+ * Gets the list of callbacks.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@Nullable
@VisibleForTesting
@UnsupportedAppUsage
+ @Deprecated
public List<ClientTransactionItem> getCallbacks() {
return mActivityCallbacks;
}
- /** Get the target state lifecycle request. */
+ /**
+ * Gets the target state lifecycle request.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@VisibleForTesting(visibility = PACKAGE)
@UnsupportedAppUsage
+ @Deprecated
public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
/**
- * Set the lifecycle state in which the client should be after executing the transaction.
+ * Sets the lifecycle state in which the client should be after executing the transaction.
* @param stateRequest A lifecycle request initialized with right parameters.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
+ @Deprecated
public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
mLifecycleStateRequest = stateRequest;
}
@@ -103,6 +143,14 @@
* requested by transaction items.
*/
public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
+ if (mTransactionItems != null) {
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; ++i) {
+ mTransactionItems.get(i).preExecute(clientTransactionHandler);
+ }
+ return;
+ }
+
if (mActivityCallbacks != null) {
final int size = mActivityCallbacks.size();
for (int i = 0; i < size; ++i) {
@@ -147,6 +195,13 @@
@Override
public void recycle() {
+ if (mTransactionItems != null) {
+ int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).recycle();
+ }
+ mTransactionItems = null;
+ }
if (mActivityCallbacks != null) {
int size = mActivityCallbacks.size();
for (int i = 0; i < size; i++) {
@@ -165,8 +220,15 @@
// Parcelable implementation
/** Write to Parcel. */
+ @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ final boolean writeTransactionItems = mTransactionItems != null;
+ dest.writeBoolean(writeTransactionItems);
+ if (writeTransactionItems) {
+ dest.writeParcelableList(mTransactionItems, flags);
+ }
+
dest.writeParcelable(mLifecycleStateRequest, flags);
final boolean writeActivityCallbacks = mActivityCallbacks != null;
dest.writeBoolean(writeActivityCallbacks);
@@ -177,11 +239,20 @@
/** Read from Parcel. */
private ClientTransaction(@NonNull Parcel in) {
- mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+ final boolean readTransactionItems = in.readBoolean();
+ if (readTransactionItems) {
+ mTransactionItems = new ArrayList<>();
+ in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
+ ClientTransactionItem.class);
+ }
+
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
+ ActivityLifecycleItem.class);
final boolean readActivityCallbacks = in.readBoolean();
if (readActivityCallbacks) {
mActivityCallbacks = new ArrayList<>();
- in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
+ ClientTransactionItem.class);
}
}
@@ -209,7 +280,8 @@
return false;
}
final ClientTransaction other = (ClientTransaction) o;
- return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ return Objects.equals(mTransactionItems, other.mTransactionItems)
+ && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
&& Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
&& mClient == other.mClient;
}
@@ -217,6 +289,7 @@
@Override
public int hashCode() {
int result = 17;
+ result = 31 * result + Objects.hashCode(mTransactionItems);
result = 31 * result + Objects.hashCode(mActivityCallbacks);
result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
result = 31 * result + Objects.hashCode(mClient);
@@ -227,6 +300,22 @@
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
pw.append(prefix).println("ClientTransaction{");
+ if (mTransactionItems != null) {
+ pw.append(prefix).print(" transactionItems=[");
+ final String itemPrefix = prefix + " ";
+ final int size = mTransactionItems.size();
+ if (size > 0) {
+ pw.println();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
+ }
+ pw.append(prefix).println(" ]");
+ } else {
+ pw.println("]");
+ }
+ pw.append(prefix).println("}");
+ return;
+ }
pw.append(prefix).print(" callbacks=[");
final String itemPrefix = prefix + " ";
final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 07e5a7d..f94e22d 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,6 +72,13 @@
return null;
}
+ /**
+ * Whether this is a {@link ActivityLifecycleItem}.
+ */
+ boolean isActivityLifecycleItem() {
+ return false;
+ }
+
/** Dumps this transaction item. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index e2aee83..7418c06 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -20,46 +20,31 @@
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 @@
/** 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 @@
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/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 066f9fe..9f5e0dc 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,6 +28,7 @@
import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
+import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
@@ -61,6 +62,9 @@
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
+ /** Keeps track of display ids whose Configuration got updated within a transaction. */
+ private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
mTransactionHandler = clientTransactionHandler;
@@ -79,15 +83,52 @@
Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
}
- executeCallbacks(transaction);
- executeLifecycleState(transaction);
+ if (transaction.getTransactionItems() != null) {
+ executeTransactionItems(transaction);
+ } else {
+ // TODO(b/260873529): cleanup after launch.
+ executeCallbacks(transaction);
+ executeLifecycleState(transaction);
+ }
+
+ if (!mConfigUpdatedDisplayIds.isEmpty()) {
+ // Whether this transaction should trigger DisplayListener#onDisplayChanged.
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final int displayCount = mConfigUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
+ controller.onDisplayChanged(displayId);
+ }
+ mConfigUpdatedDisplayIds.clear();
+ }
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
- /** Cycle through all states requested by callbacks and execute them at proper times. */
+ /** Cycles through all transaction items and execute them at proper times. */
@VisibleForTesting
+ public void executeTransactionItems(@NonNull ClientTransaction transaction) {
+ final List<ClientTransactionItem> items = transaction.getTransactionItems();
+ final int size = items.size();
+ for (int i = 0; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem()) {
+ executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
+ } else {
+ executeNonLifecycleItem(transaction, item,
+ shouldExcludeLastLifecycleState(items, i));
+ }
+ }
+ }
+
+ /**
+ * Cycle through all states requested by callbacks and execute them at proper times.
+ * @deprecated use {@link #executeTransactionItems} instead.
+ */
+ @VisibleForTesting
+ @Deprecated
public void executeCallbacks(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()) {
@@ -105,83 +146,78 @@
// Index of the last callback that requests some post-execution state.
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
- // Keep track of display ids whose Configuration got updated with this transaction.
- ArraySet<Integer> configUpdatedDisplays = null;
-
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
- final IBinder token = item.getActivityToken();
- ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
- if (token != null && r == null
- && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
- // The activity has not been created but has been requested to destroy, so all
- // transactions for the token are just like being cancelled.
- Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
- continue;
- }
-
- if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ // Skip the very last transition and perform it by explicit state request instead.
final int postExecutionState = item.getPostExecutionState();
-
- if (item.shouldHaveDefinedPreExecutionState()) {
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- item.getPostExecutionState());
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
- }
- }
-
- // Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
- ? item.getContextToUpdate(mTransactionHandler)
- : null;
- final Configuration preExecutedConfig = configUpdatedContext != null
- ? new Configuration(configUpdatedContext.getResources().getConfiguration())
- : null;
-
- item.execute(mTransactionHandler, mPendingActions);
-
- if (configUpdatedContext != null) {
- final Configuration postExecutedConfig = configUpdatedContext.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
- if (configUpdatedDisplays == null) {
- configUpdatedDisplays = new ArraySet<>();
- }
- configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
- }
- }
-
- item.postExecute(mTransactionHandler, mPendingActions);
- if (r == null) {
- // Launch activity request will create an activity record.
- r = mTransactionHandler.getActivityClient(token);
- }
-
- if (postExecutionState != UNDEFINED && r != null) {
- // Skip the very last transition and perform it by explicit state request instead.
- final boolean shouldExcludeLastTransition =
- i == lastCallbackRequestingState && finalState == postExecutionState;
- cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
- }
- }
-
- if (configUpdatedDisplays != null) {
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = configUpdatedDisplays.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplays.valueAt(i);
- controller.onDisplayChanged(displayId);
- }
+ final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
+ && i == lastCallbackRequestingState && finalState == postExecutionState;
+ executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
}
}
- /** Transition to the final state if requested by the transaction. */
+ private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
+ final IBinder token = item.getActivityToken();
+ ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+
+ if (token != null && r == null
+ && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+ return;
+ }
+
+ if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ final int postExecutionState = item.getPostExecutionState();
+
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ postExecutionState);
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
+ }
+ }
+
+ // Can't read flag from isolated process.
+ final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+ && syncWindowConfigUpdateFlag();
+ final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ ? item.getContextToUpdate(mTransactionHandler)
+ : null;
+ final Configuration preExecutedConfig = configUpdatedContext != null
+ ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+ : null;
+
+ item.execute(mTransactionHandler, mPendingActions);
+
+ if (configUpdatedContext != null) {
+ final Configuration postExecutedConfig = configUpdatedContext.getResources()
+ .getConfiguration();
+ if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+ mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
+ }
+ }
+
+ item.postExecute(mTransactionHandler, mPendingActions);
+ if (r == null) {
+ // Launch activity request will create an activity record.
+ r = mTransactionHandler.getActivityClient(token);
+ }
+
+ if (postExecutionState != UNDEFINED && r != null) {
+ cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
+ }
+ }
+
+ /**
+ * Transition to the final state if requested by the transaction.
+ * @deprecated use {@link #executeTransactionItems} instead
+ */
+ @Deprecated
private void executeLifecycleState(@NonNull ClientTransaction transaction) {
final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
if (lifecycleItem == null) {
@@ -189,6 +225,11 @@
return;
}
+ executeLifecycleItem(transaction, lifecycleItem);
+ }
+
+ private void executeLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ActivityLifecycleItem lifecycleItem) {
final IBinder token = lifecycleItem.getActivityToken();
final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 7e89a5b..dfbccb4 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,21 +236,39 @@
* index 1 will be returned, because ActivityResult request on position 1 will be the last
* request that moves activity to the RESUMED state where it will eventually end.
*/
- static int lastCallbackRequestingState(ClientTransaction transaction) {
+ static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null || callbacks.size() == 0) {
+ if (callbacks == null || callbacks.isEmpty()
+ || transaction.getLifecycleStateRequest() == null) {
return -1;
}
+ return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
+ transaction.getLifecycleStateRequest().getActivityToken());
+ }
+ /**
+ * Returns the index of the last callback between the start index and last index that requests
+ * the state for the given activity token in which that activity will be after execution.
+ * If there is a group of callbacks in the end that requests the same specific state or doesn't
+ * request any - we will find the first one from such group.
+ *
+ * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+ * specific state. If there is a sequence
+ * Configuration - ActivityResult - Configuration - ActivityResult
+ * index 1 will be returned, because ActivityResult request on position 1 will be the last
+ * request that moves activity to the RESUMED state where it will eventually end.
+ */
+ private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, int lastIndex, @NonNull IBinder activityToken) {
// Go from the back of the list to front, look for the request closes to the beginning that
// requests the state in which activity will end after all callbacks are executed.
int lastRequestedState = UNDEFINED;
int lastRequestingCallback = -1;
- for (int i = callbacks.size() - 1; i >= 0; i--) {
- final ClientTransactionItem callback = callbacks.get(i);
- final int postExecutionState = callback.getPostExecutionState();
- if (postExecutionState != UNDEFINED) {
- // Found a callback that requests some post-execution state.
+ for (int i = lastIndex; i >= startIndex; i--) {
+ final ClientTransactionItem item = items.get(i);
+ final int postExecutionState = item.getPostExecutionState();
+ if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
+ // Found a callback that requests some post-execution state for the given activity.
if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
// It's either a first-from-end callback that requests state or it requests
// the same state as the last one. In both cases, we will use it as the new
@@ -266,6 +284,53 @@
return lastRequestingCallback;
}
+ /**
+ * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
+ * whether or not to exclude the last state. This only returns {@code true} when there is a
+ * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
+ * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
+ */
+ static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
+ int currentIndex) {
+ final ClientTransactionItem item = items.get(currentIndex);
+ final IBinder activityToken = item.getActivityToken();
+ final int postExecutionState = item.getPostExecutionState();
+ if (activityToken == null || postExecutionState == UNDEFINED) {
+ // Not a transaction item requesting post execution state.
+ return false;
+ }
+ final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
+ activityToken);
+ if (nextLifecycleItemIndex == -1) {
+ // No following ActivityLifecycleItem for this activity token.
+ return false;
+ }
+ final ActivityLifecycleItem lifecycleItem =
+ (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
+ if (postExecutionState != lifecycleItem.getTargetState()) {
+ // The explicit ActivityLifecycleItem is not requesting the same state.
+ return false;
+ }
+ // Only exclude for the first non-lifecycle item that requests the same specific state.
+ return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
+ nextLifecycleItemIndex - 1, activityToken);
+ }
+
+ /**
+ * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
+ */
+ private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, @NonNull IBinder activityToken) {
+ final int size = items.size();
+ for (int i = startIndex; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
/** Dump transaction to string. */
static String transactionToString(@NonNull ClientTransaction transaction,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 3c66a15..f6f65c7 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.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 @@
* {@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 @@
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 @@
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 @@
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 @@
mSliceUri = sliceUri;
mWidget = widget;
mTemplateData = templateData;
+ mRemoteViews = remoteViews;
}
/**
@@ -461,6 +470,15 @@
}
/**
+ * 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 @@
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 @@
+ ", mSliceUri=" + mSliceUri
+ ", mWidget=" + mWidget
+ ", mTemplateData=" + mTemplateData
+ + ", mRemoteViews=" + mRemoteViews
+ '}';
}
@@ -550,7 +570,8 @@
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 @@
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 @@
private AppWidgetProviderInfo mWidget;
private BaseTemplateData mTemplateData;
+ private RemoteViews mRemoteViews;
+
/**
* A builder for {@link SmartspaceTarget}.
*
@@ -727,9 +750,15 @@
*
* <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 @@
}
/**
+ * 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 @@
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 0000000..12af888
--- /dev/null
+++ b/core/java/android/app/smartspace/flags.aconfig
@@ -0,0 +1,15 @@
+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"
+}
+
+flag {
+ name: "access_smartspace"
+ namespace: "sysui_integrations"
+ description: "Flag to enable the ACCESS_SMARTSPACE check in SmartspaceManagerService"
+ bug: "297207196"
+}
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index a5e5135..70c25ea 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -34,8 +34,8 @@
void unregisterTrustListener(in ITrustListener trustListener);
void reportKeyguardShowingChanged();
void setDeviceLockedForUser(int userId, boolean locked);
- boolean isDeviceLocked(int userId, int displayId);
- boolean isDeviceSecure(int userId, int displayId);
+ boolean isDeviceLocked(int userId, int deviceId);
+ boolean isDeviceSecure(int userId, int deviceId);
@EnforcePermission("TRUST_LISTENER")
boolean isTrustUsuallyManaged(int userId);
void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/app/usage/ParcelableUsageEventList.aidl
similarity index 78%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
copy to core/java/android/app/usage/ParcelableUsageEventList.aidl
index 6d7c576..1652996 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 0000000..016d97f
--- /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 3c256ad..c188686 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -714,7 +714,7 @@
@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 @@
*/
@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 @@
mParcel.setDataSize(mParcel.dataPosition());
mParcel.setDataPosition(positionInParcel);
}
- mIncludeTaskRoots = true;
}
/**
@@ -810,6 +826,10 @@
return false;
}
+ if (Flags.useParceledList()) {
+ return getNextEventFromParceledList(eventOut);
+ }
+
if (mParcel != null) {
readEventFromParcel(mParcel, eventOut);
} else {
@@ -824,6 +844,12 @@
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 @@
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 @@
@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 4f1c65b..0b8e29f 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -21,3 +21,10 @@
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 570ecaa..c99a457 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 @@
* 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 @@
* {@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 @@
* {@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 @@
* @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 4f9c849..6e33dff 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -19,4 +19,11 @@
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 62fbcaf..4b2cee6 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -155,6 +155,7 @@
/** @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 59bb73b..1c917ee 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -23,6 +23,7 @@
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 @@
* <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 @@
* @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 @@
* @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 7b6bad3..ffc4805 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6340,6 +6340,7 @@
* 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/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index aefa55f..323592c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1204,12 +1204,15 @@
/**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
- * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
- * OVERRIDE_MIN_ASPECT_RATIO_LARGE
+ * <ul>
+ * <li>OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
+ * <li>OVERRIDE_MIN_ASPECT_RATIO_LARGE
+ * </ul>
*
* If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest
* will be overridden to the largest enabled aspect ratio treatment unless the app's manifest
- * value is higher.
+ * value is higher. By default, this will only apply to activities with fixed portrait
+ * orientation if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY is not explicitly disabled.
* @hide
*/
@ChangeId
diff --git a/core/java/android/content/pm/ArchivedActivity.java b/core/java/android/content/pm/ArchivedActivity.java
new file mode 100644
index 0000000..9e49c9e
--- /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 0000000..42795db
--- /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/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7d..e9f419e 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.LocusId;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.LauncherActivityInfoInternal;
@@ -62,6 +63,7 @@
in Bundle opts, in UserHandle user);
PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
+ LauncherUserInfo getLauncherUserInfo(in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index edb07ce..59ed045 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 @@
@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/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index dbaa4c9..0cd4358 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Flags;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -776,6 +778,28 @@
}
/**
+ * Returns information related to a user which is useful for displaying UI elements
+ * to distinguish it from other users (eg, badges). Only system launchers should
+ * call this API.
+ *
+ * @param userHandle user handle of the user for which LauncherUserInfo is requested
+ * @return the LauncherUserInfo object related to the user specified.
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) {
+ if (DEBUG) {
+ Log.i(TAG, "getLauncherUserInfo " + userHandle);
+ }
+ try {
+ return mService.getLauncherUserInfo(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/content/pm/LauncherUserInfo.aidl
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
rename to core/java/android/content/pm/LauncherUserInfo.aidl
index 6d7c576..f875f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -11,12 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.qs.tiles.viewmodel
+package android.content.pm;
-enum class QSTileLifecycle {
- ALIVE,
- DEAD,
-}
+parcelable LauncherUserInfo;
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
new file mode 100644
index 0000000..214c3e4
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -0,0 +1,126 @@
+/*
+ * 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.os.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * The LauncherUserInfo object holds information about an Android user that is required to display
+ * the Launcher related UI elements specific to the user (like badges).
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+public final class LauncherUserInfo implements Parcelable {
+
+ private final String mUserType;
+
+ // Serial number for the user, should be same as in the {@link UserInfo} object.
+ private final int mUserSerialNumber;
+
+ /**
+ * Returns type of the user as defined in {@link UserManager}. e.g.,
+ * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
+ * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug
+ * is resolved.
+ *
+ * @return the userType for the user whose LauncherUserInfo this is
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ @NonNull
+ public String getUserType() {
+ return mUserType;
+ }
+
+ /**
+ * Returns serial number of user as returned by
+ * {@link UserManager#getSerialNumberForUser(UserHandle)}
+ *
+ * @return the serial number associated with the user
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int getUserSerialNumber() {
+ return mUserSerialNumber;
+ }
+
+ private LauncherUserInfo(@NonNull Parcel in) {
+ mUserType = in.readString16NoHelper();
+ mUserSerialNumber = in.readInt();
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString16NoHelper(mUserType);
+ dest.writeInt(mUserSerialNumber);
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR =
+ new Creator<LauncherUserInfo>() {
+ @Override
+ public LauncherUserInfo createFromParcel(Parcel in) {
+ return new LauncherUserInfo(in);
+ }
+
+ @Override
+ public LauncherUserInfo[] newArray(int size) {
+ return new LauncherUserInfo[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final class Builder {
+ private final String mUserType;
+
+ private final int mUserSerialNumber;
+
+ public Builder(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+
+ /**
+ * Builds the LauncherUserInfo object
+ */
+ @NonNull public LauncherUserInfo build() {
+ return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+ }
+
+ } // End builder
+
+ private LauncherUserInfo(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d837aae..cbb20e0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1000,6 +1000,37 @@
}
}
+ /**
+ * 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)
@@ -2504,6 +2535,8 @@
public DataLoaderParams dataLoaderParams;
/** {@hide} */
public int rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+ /** @hide */
+ public long rollbackLifetimeMillis = 0;
/** {@hide} */
public boolean forceQueryableOverride;
/** {@hide} */
@@ -2558,6 +2591,7 @@
dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
}
rollbackDataPolicy = source.readInt();
+ rollbackLifetimeMillis = source.readLong();
requireUserAction = source.readInt();
packageSource = source.readInt();
applicationEnabledSettingPersistent = source.readBoolean();
@@ -2590,6 +2624,7 @@
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
ret.dataLoaderParams = dataLoaderParams;
ret.rollbackDataPolicy = rollbackDataPolicy;
+ ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -2871,12 +2906,7 @@
*/
@SystemApi
public void setEnableRollback(boolean enable) {
- if (enable) {
- installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
- } else {
- installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
- }
- rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+ setEnableRollback(enable, PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
}
/**
@@ -2900,10 +2930,36 @@
installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
} else {
installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ rollbackLifetimeMillis = 0;
}
rollbackDataPolicy = dataPolicy;
}
+ /**
+ * If rollback enabled for this session (via {@link #setEnableRollback}, set time
+ * after which rollback will no longer be possible
+ *
+ * <p>For multi-package installs, this value must be set on the parent session.
+ * Child session rollback lifetime will be ignored.
+ *
+ * @param lifetimeMillis time after which rollback expires
+ * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not
+ * enabled via setEnableRollback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+ @FlaggedApi(Flags.FLAG_ROLLBACK_LIFETIME)
+ public void setRollbackLifetimeMillis(@DurationMillisLong long lifetimeMillis) {
+ if (lifetimeMillis < 0) {
+ throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+ }
+ if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackLifetimeMillis when rollback is not enabled");
+ }
+ rollbackLifetimeMillis = lifetimeMillis;
+ }
/**
* @deprecated use {@link #setRequestDowngrade(boolean)}.
@@ -3264,6 +3320,7 @@
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
pw.printPair("dataLoaderParams", dataLoaderParams);
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+ pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
pw.printPair("applicationEnabledSettingPersistent",
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3305,6 +3362,7 @@
dest.writeParcelable(null, flags);
}
dest.writeInt(rollbackDataPolicy);
+ dest.writeLong(rollbackLifetimeMillis);
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3498,6 +3556,9 @@
/** {@hide} */
public int rollbackDataPolicy;
+ /** @hide */
+ public long rollbackLifetimeMillis;
+
/** {@hide} */
public int requireUserAction;
@@ -3565,6 +3626,7 @@
isCommitted = source.readBoolean();
isPreapprovalRequested = source.readBoolean();
rollbackDataPolicy = source.readInt();
+ rollbackLifetimeMillis = source.readLong();
createdMillis = source.readLong();
requireUserAction = source.readInt();
installerUid = source.readInt();
@@ -4189,6 +4251,7 @@
dest.writeBoolean(isCommitted);
dest.writeBoolean(isPreapprovalRequested);
dest.writeInt(rollbackDataPolicy);
+ dest.writeLong(rollbackLifetimeMillis);
dest.writeLong(createdMillis);
dest.writeInt(requireUserAction);
dest.writeInt(installerUid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b15c9e4..ad7dd51 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3778,6 +3778,7 @@
* 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 @@
"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 ee9aaca3..543703e 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,9 +16,16 @@
package android.content.pm;
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.security.PublicKey;
+import java.util.Collection;
/**
* Information pertaining to the signing certificates used to sign a package.
@@ -33,6 +40,43 @@
}
/**
+ * Creates a new instance of information used to sign the APK.
+ *
+ * @param schemeVersion version of signing schema.
+ * @param apkContentsSigners signing certificates.
+ * @param publicKeys for the signing certificates.
+ * @param signingCertificateHistory All signing certificates the package has proven it is
+ * authorized to use.
+ *
+ * @see
+ * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
+ * schemas</a>
+ */
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public SigningInfo(@IntRange(from = 0) int schemeVersion,
+ @Nullable Collection<Signature> apkContentsSigners,
+ @Nullable Collection<PublicKey> publicKeys,
+ @Nullable Collection<Signature> signingCertificateHistory) {
+ if (schemeVersion <= 0 || apkContentsSigners == null) {
+ mSigningDetails = SigningDetails.UNKNOWN;
+ return;
+ }
+ Signature[] signatures = apkContentsSigners != null && !apkContentsSigners.isEmpty()
+ ? apkContentsSigners.toArray(new Signature[apkContentsSigners.size()])
+ : null;
+ Signature[] pastSignatures =
+ signingCertificateHistory != null && !signingCertificateHistory.isEmpty()
+ ? signingCertificateHistory.toArray(new Signature[signingCertificateHistory.size()])
+ : null;
+ if (Signature.areExactArraysMatch(signatures, pastSignatures)) {
+ pastSignatures = null;
+ }
+ ArraySet<PublicKey> keys =
+ publicKeys != null && !publicKeys.isEmpty() ? new ArraySet<>(publicKeys) : null;
+ mSigningDetails = new SigningDetails(signatures, schemeVersion, keys, pastSignatures);
+ }
+
+ /**
* @hide only packagemanager should be populating this
*/
public SigningInfo(SigningDetails signingDetails) {
@@ -116,6 +160,26 @@
return mSigningDetails.getSignatures();
}
+ /**
+ * Returns the version of signing schema used to sign the APK.
+ *
+ * @see
+ * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
+ * schemas</a>
+ */
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public @IntRange(from = 0) int getSchemeVersion() {
+ return mSigningDetails.getSignatureSchemeVersion();
+ }
+
+ /**
+ * Returns the public keys for the signing certificates.
+ */
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public @NonNull Collection<PublicKey> getPublicKeys() {
+ return mSigningDetails.getPublicKeys();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -126,6 +190,12 @@
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/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index db12728..9ad66ce 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -15,9 +15,9 @@
}
flag {
- name: "prevent_sdk_lib_app"
+ name: "disallow_sdk_libs_to_be_apps"
namespace: "package_manager_service"
- description: "Feature flag to enable the prevent sdk-library be an application."
+ description: "Feature flag to disallow a <sdk-library> to be an <application>."
bug: "295843617"
is_fixed_read_only: true
}
@@ -58,3 +58,11 @@
bug: "295827951"
is_fixed_read_only: true
}
+
+flag {
+ name: "rollback_lifetime"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable custom rollback lifetime during install."
+ bug: "299670324"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 3ec239c..43cf97f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -28,3 +28,10 @@
description: "Framework support for communal profile."
bug: "285426179"
}
+
+flag {
+ name: "support_communal_profile_nextgen"
+ namespace: "multiuser"
+ description: "Further framework support for communal profile, beyond the basics, for later releases."
+ bug: "285426179"
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 20771af..524afe9 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -153,7 +153,7 @@
mService.getCandidateCredentials(
request,
new GetCandidateCredentialsTransport(executor, callback),
- mContext.getOpPackageName());
+ callingPackage);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 8bfc2f7..014cf6d 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -16,21 +16,28 @@
package android.hardware;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.hardware.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import libcore.util.NativeAllocationRegistry;
/**
- * The class provides overlay properties of the device. OverlayProperties
- * exposes some capabilities from HWC e.g. if fp16 can be supported for HWUI.
+ * Provides supported overlay properties of the device.
*
- * In the future, more capabilities can be added, e.g., whether or not
- * per-layer colorspaces are supported.
- *
- * @hide
+ * <p>
+ * Hardware overlay is a technique to composite different buffers directly
+ * to the screen using display hardware rather than the GPU.
+ * The system compositor is able to assign any content managed by a
+ * {@link android.view.SurfaceControl} onto a hardware overlay if possible.
+ * Applications may be interested in the display hardware capabilities exposed
+ * by this class as a hint to determine if their {@link android.view.SurfaceControl}
+ * tree is power-efficient and performant.
+ * </p>
*/
+@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public final class OverlayProperties implements Parcelable {
private static final NativeAllocationRegistry sRegistry =
@@ -38,10 +45,12 @@
nGetDestructor());
private long mNativeObject;
+ // only for virtual displays
+ private static OverlayProperties sDefaultOverlayProperties;
// Invoked on destruction
private Runnable mCloser;
- public OverlayProperties(long nativeObject) {
+ private OverlayProperties(long nativeObject) {
if (nativeObject != 0) {
mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
}
@@ -49,7 +58,20 @@
}
/**
+ * For virtual displays, we provide an overlay properties object
+ * with RGBA 8888 only, sRGB only, true for mixed color spaces.
+ * @hide
+ */
+ public static OverlayProperties getDefault() {
+ if (sDefaultOverlayProperties == null) {
+ sDefaultOverlayProperties = new OverlayProperties(nCreateDefault());
+ }
+ return sDefaultOverlayProperties;
+ }
+
+ /**
* @return True if the device can support fp16, false otherwise.
+ * @hide
*/
public boolean supportFp16ForHdr() {
if (mNativeObject == 0) {
@@ -59,8 +81,13 @@
}
/**
- * @return True if the device can support mixed colorspaces, false otherwise.
+ * Indicates that hw composition of two or more overlays
+ * with different colorspaces is supported on the device.
+ *
+ * @return True if the device can support mixed colorspaces efficiently,
+ * false if GPU composition fallback is otherwise required.
*/
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean supportMixedColorSpaces() {
if (mNativeObject == 0) {
return false;
@@ -68,28 +95,14 @@
return nSupportMixedColorSpaces(mNativeObject);
}
- /**
- * Release the local reference.
- */
- public void release() {
- if (mNativeObject != 0) {
- mCloser.run();
- mNativeObject = 0;
- }
- }
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public int describeContents() {
return 0;
}
- /**
- * Flatten this object in to a Parcel.
- *
- * @param dest The Parcel in which the object should be written.
- * @param flags Additional flags about how the object should be written.
- * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
- */
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
if (mNativeObject == 0) {
@@ -100,6 +113,7 @@
nWriteOverlayPropertiesToParcel(mNativeObject, dest);
}
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public static final @NonNull Parcelable.Creator<OverlayProperties> CREATOR =
new Parcelable.Creator<OverlayProperties>() {
public OverlayProperties createFromParcel(Parcel in) {
@@ -115,6 +129,7 @@
};
private static native long nGetDestructor();
+ private static native long nCreateDefault();
private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 9c05dfc..82694ee 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -537,24 +537,6 @@
}
/**
- * 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 8eede47..c2e5c0b 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 @@
// 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 36606a1..18c8d1b 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 @@
@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 bf77681..db7055b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -357,7 +357,7 @@
mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
}
mRepeatingRequestImageCallback = new CameraOutputImageCallback(
- mRepeatingRequestImageReader);
+ mRepeatingRequestImageReader, true /*pruneOlderBuffers*/);
mRepeatingRequestImageReader
.setOnImageAvailableListener(mRepeatingRequestImageCallback, mHandler);
}
@@ -398,7 +398,8 @@
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 @@
}
for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) {
- captureStage.first.close();
+ if (captureStage.first != null) {
+ captureStage.first.close();
+ }
}
mCaptureStageMap.clear();
}
@@ -1207,6 +1210,7 @@
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 @@
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 @@
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 @@
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 5e53373..95526a8 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1403,6 +1403,7 @@
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 aeddd0c..8e49c4c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -46,6 +46,7 @@
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 @@
@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 2b5f5ee..75f0ceb 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -42,7 +42,6 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
}
- 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 @@
// 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 @@
Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
event));
}
- handleDisplayEvent(displayId, event);
+ handleDisplayEvent(displayId, event, false /* forceUpdate */);
}
}
@@ -1197,87 +1210,86 @@
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;
}
@@ -1423,12 +1435,10 @@
sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
&& EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
}
- // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
- return true;
+ return sExtraDisplayListenerLogging;
}
private static boolean extraLogging() {
- // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
- return true;
+ return sExtraDisplayListenerLogging;
}
}
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
new file mode 100644
index 0000000..c6a352e
--- /dev/null
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.flags"
+
+flag {
+ name: "overlayproperties_class_api"
+ namespace: "core_graphics"
+ description: "public OverlayProperties class, OverlayProperties#supportMixedColorSpaces and Display#getOverlaySupport API"
+ bug: "267234573"
+}
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 861e440..6952e5d 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -2,5 +2,4 @@
include /services/core/java/com/android/server/display/OWNERS
-marvinramin@google.com
-lcnathalie@google.com
+quxiangfang@google.com
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 4f07acf..c5167db 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 @@
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 @@
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 @@
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 @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 @@
/**
* 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 @@
}
/**
+ * 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 12442ba..c7ec052 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 @@
@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 @@
*
* <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 @@
*
* <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 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 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 @@
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 8c6083c..237ec01 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -18,6 +18,7 @@
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 @@
/**
* 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 @@
/** 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 @@
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 @@
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 @@
}
/**
- * {@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 @@
}
/**
+ * @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 b7bf783..db14c08 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -142,12 +144,84 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* <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 @@
* <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 @@
}
/**
+ * 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 bdbca91..ba31ca3 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -363,7 +363,7 @@
@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 @@
@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 @@
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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4c2bbc1..ba80811 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3363,6 +3363,13 @@
return true;
}
return false;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
+ if (mDecorViewVisible && mWindowVisible) {
+ int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mPrivOps.switchKeyboardLayoutAsync(direction);
+ return true;
+ }
}
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index a40fb15..66e3c28 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,10 +16,12 @@
package android.net.vcn;
import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -235,6 +237,9 @@
"mMinUdpPort4500NatTimeoutSeconds";
private final int mMinUdpPort4500NatTimeoutSeconds;
+ private static final String IS_SAFE_MODE_DISABLED_KEY = "mIsSafeModeDisabled";
+ private final boolean mIsSafeModeDisabled;
+
private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
@NonNull private final Set<Integer> mGatewayOptions;
@@ -247,6 +252,7 @@
@NonNull long[] retryIntervalsMs,
@IntRange(from = MIN_MTU_V6) int maxMtu,
@NonNull int minUdpPort4500NatTimeoutSeconds,
+ boolean isSafeModeDisabled,
@NonNull Set<Integer> gatewayOptions) {
mGatewayConnectionName = gatewayConnectionName;
mTunnelConnectionParams = tunnelConnectionParams;
@@ -255,6 +261,7 @@
mMaxMtu = maxMtu;
mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions));
+ mIsSafeModeDisabled = isSafeModeDisabled;
mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
if (mUnderlyingNetworkTemplates.isEmpty()) {
@@ -317,6 +324,7 @@
in.getInt(
MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY,
MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
+ mIsSafeModeDisabled = in.getBoolean(IS_SAFE_MODE_DISABLED_KEY);
validate();
}
@@ -483,6 +491,17 @@
}
/**
+ * Check whether safe mode is enabled
+ *
+ * @see Builder#enableSafeMode(boolean)
+ * @hide
+ */
+ @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+ public boolean isSafeModeEnabled() {
+ return !mIsSafeModeDisabled;
+ }
+
+ /**
* Checks if the given VCN gateway option is enabled.
*
* @param option the option to check.
@@ -528,6 +547,7 @@
result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
result.putInt(MAX_MTU_KEY, mMaxMtu);
result.putInt(MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, mMinUdpPort4500NatTimeoutSeconds);
+ result.putBoolean(IS_SAFE_MODE_DISABLED_KEY, mIsSafeModeDisabled);
return result;
}
@@ -542,6 +562,7 @@
Arrays.hashCode(mRetryIntervalsMs),
mMaxMtu,
mMinUdpPort4500NatTimeoutSeconds,
+ mIsSafeModeDisabled,
mGatewayOptions);
}
@@ -559,6 +580,7 @@
&& Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
&& mMaxMtu == rhs.mMaxMtu
&& mMinUdpPort4500NatTimeoutSeconds == rhs.mMinUdpPort4500NatTimeoutSeconds
+ && mIsSafeModeDisabled == rhs.mIsSafeModeDisabled
&& mGatewayOptions.equals(rhs.mGatewayOptions);
}
@@ -577,6 +599,7 @@
@NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
private int mMaxMtu = DEFAULT_MAX_MTU;
private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
+ private boolean mIsSafeModeDisabled = false;
@NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
@@ -789,6 +812,28 @@
}
/**
+ * Enable/disable safe mode
+ *
+ * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
+ * safe mode. In safe mode, the VCN Network will be torn down and the system will restore
+ * connectivity by allowing underlying cellular networks to be used as default. At the same
+ * time, VCN will continue to retry until it succeeds.
+ *
+ * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
+ * might not have connectivity, and may not have access to carrier-owned underlying
+ * networks.
+ *
+ * @param enabled whether safe mode should be enabled. Defaults to {@code true}
+ * @hide
+ */
+ @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+ @NonNull
+ public Builder enableSafeMode(boolean enabled) {
+ mIsSafeModeDisabled = !enabled;
+ return this;
+ }
+
+ /**
* Builds and validates the VcnGatewayConnectionConfig.
*
* @return an immutable VcnGatewayConnectionConfig instance
@@ -803,6 +848,7 @@
mRetryIntervalsMs,
mMaxMtu,
mMinUdpPort4500NatTimeoutSeconds,
+ mIsSafeModeDisabled,
mGatewayOptions);
}
}
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
new file mode 100644
index 0000000..6956916
--- /dev/null
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.net.vcn"
+
+flag {
+ name: "safe_mode_config"
+ namespace: "vcn"
+ description: "Feature flag for safe mode configurability"
+ bug: "276358140"
+}
\ No newline at end of file
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 958669e..ae3e333 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Pattern;
/**********************************************************************
* This file is not a part of the NFC mainline module *
@@ -79,7 +80,7 @@
throw new IllegalArgumentException("Too many AIDs in AID group.");
}
for (String aid : aids) {
- if (!CardEmulation.isValidAid(aid)) {
+ if (!isValidAid(aid)) {
throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
}
}
@@ -264,4 +265,34 @@
return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
CardEmulation.CATEGORY_OTHER.equals(category);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 18ec914..665b753 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Class holding APDU service info.
@@ -307,7 +308,7 @@
com.android.internal.R.styleable.AidFilter);
String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
toUpperCase();
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -321,7 +322,7 @@
toUpperCase();
// Add wildcard char to indicate prefix
aid = aid.concat("*");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -335,7 +336,7 @@
toUpperCase();
// Add wildcard char to indicate suffix
aid = aid.concat("#");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -806,7 +807,7 @@
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
if (!mOnHost) {
@@ -825,4 +826,34 @@
}
proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index ec919e4..33bc169 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@
com.android.internal.R.styleable.SystemCodeFilter);
systemCode = a.getString(
com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
- if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
+ if (!isValidSystemCode(systemCode) &&
!systemCode.equalsIgnoreCase("NULL")) {
Log.e(TAG, "Invalid System Code: " + systemCode);
systemCode = null;
@@ -187,7 +187,7 @@
com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
if (!nfcid2.equalsIgnoreCase("RANDOM") &&
!nfcid2.equalsIgnoreCase("NULL") &&
- !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
+ !isValidNfcid2(nfcid2)) {
Log.e(TAG, "Invalid NFCID2: " + nfcid2);
nfcid2 = null;
}
@@ -436,10 +436,62 @@
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
}
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+ * @hide
+ */
+ private static boolean isValidSystemCode(String systemCode) {
+ if (systemCode == null) {
+ return false;
+ }
+ if (systemCode.length() != 4) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+ if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ try {
+ Integer.parseInt(systemCode, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+ * @hide
+ */
+ private static boolean isValidNfcid2(String nfcid2) {
+ if (nfcid2 == null) {
+ return false;
+ }
+ if (nfcid2.length() != 16) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ // check if the the value starts with "02FE"
+ if (!nfcid2.toUpperCase().startsWith("02FE")) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ try {
+ Long.parseLong(nfcid2, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ return true;
+ }
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 55b0b42..cd50ace 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -13,3 +13,10 @@
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/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 58def6e..960e84d 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -26,6 +26,7 @@
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.content.Context;
@@ -280,8 +281,8 @@
*
* <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
*
- * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
- * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
+ * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not
+ * consented on previous attempts.
*
* @param bugreportFile the identifier for a bugreport that was previously generated for this
* caller using {@code startBugreport}.
@@ -294,6 +295,7 @@
@SystemApi
@RequiresPermission(Manifest.permission.DUMP)
@WorkerThread
+ @UserHandleAware
public void retrieveBugreport(
@NonNull String bugreportFile,
@NonNull ParcelFileDescriptor bugreportFd,
@@ -307,8 +309,10 @@
Preconditions.checkNotNull(callback);
DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+ mContext.getUserId(),
bugreportFd.getFileDescriptor(),
bugreportFile,
+ /* keepBugreportOnRetrieval = */ false,
dsListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index e8ad303..8510084 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -128,6 +129,8 @@
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
/**
@@ -145,7 +148,8 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
- BUGREPORT_FLAG_DEFER_CONSENT
+ BUGREPORT_FLAG_DEFER_CONSENT,
+ BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
})
public @interface BugreportFlag {}
@@ -165,4 +169,20 @@
* String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
*/
public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
+
+ /**
+ * Flag for keeping a bugreport stored even after it has been retrieved via
+ * {@link BugreportManager#retrieveBugreport}.
+ *
+ * <p>This flag can only be used when {@link #BUGREPORT_FLAG_DEFER_CONSENT} is set.
+ * The bugreport may be retrieved multiple times using
+ * {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
+ IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL;
}
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index cf35460..a1d2dcc 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 @@
/**
* 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 @@
/**
* 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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 839c56f..330b992 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -137,6 +137,7 @@
boolean isUserVisible(int userId);
int[] getVisibleUsers();
int getMainDisplayIdAssignedToUser();
+ boolean isForegroundUserAdmin();
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles(int userId);
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index cbc9213..11084b8 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 @@
*
* @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/PowerManager.java b/core/java/android/os/PowerManager.java
index fce715a..d2c1755 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
@@ -1940,6 +1941,7 @@
*
* @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
@TestApi
public boolean isBatterySaverSupported() {
try {
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 81d4e3a..47b6d8d 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2367,14 +2367,14 @@
}
/** 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 @@
// 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 9034ff1..cc79ae3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -23,6 +23,7 @@
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.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 @@
* @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";
@@ -2771,6 +2774,8 @@
* Returns the designated "communal profile" of the device, or {@code null} if there is none.
* @hide
*/
+ @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE)
+ @TestApi
@RequiresPermission(anyOf = {
Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS,
@@ -2788,16 +2793,33 @@
}
/**
+ * Checks if the context user is running in a communal profile.
+ *
+ * A communal profile is a {@link #isProfile() profile}, but instead of being associated with a
+ * particular parent user, it is communal to the device.
+ *
+ * @return whether the context user is a communal profile.
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE)
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public boolean isCommunalProfile() {
+ return isCommunalProfile(mUserId);
+ }
+
+ /**
* Returns {@code true} if the given user is the designated "communal profile" of the device.
* @hide
*/
@RequiresPermission(anyOf = {
- Manifest.permission.MANAGE_USERS,
- Manifest.permission.CREATE_USERS,
- Manifest.permission.QUERY_USERS})
- public boolean isCommunalProfile(@UserIdInt int userId) {
- final UserInfo user = getUserInfo(userId);
- return user != null && user.isCommunalProfile();
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ private boolean isCommunalProfile(@UserIdInt int userId) {
+ return isUserTypeCommunalProfile(getProfileType(userId));
}
/**
@@ -2837,6 +2859,23 @@
}
/**
+ * Used to check if the user currently running in the <b>foreground</b> is an
+ * {@link #isAdminUser() admin} user.
+ *
+ * @return whether the foreground user is an admin user.
+ * @see #isAdminUser()
+ * @see #isUserForeground()
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE_NEXTGEN)
+ public boolean isForegroundUserAdmin() {
+ try {
+ return mService.isForegroundUserAdmin();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns whether the context user is of the given user type.
*
* @param userType the name of the user's user type, e.g.
@@ -2994,7 +3033,7 @@
}
/**
- * Checks if the calling context user can have a restricted profile.
+ * Checks if the context user can have a restricted profile.
* @return whether the context user can have a restricted profile.
* @hide
*/
@@ -3098,11 +3137,11 @@
}
/**
- * Checks if the calling context user is running in a profile. A profile is a user that
+ * Checks if the context user is running in a profile. A profile is a user that
* typically has its own separate data but shares its UI with some parent user. For example, a
* {@link #isManagedProfile() managed profile} is a type of profile.
*
- * @return whether the caller is in a profile.
+ * @return whether the context user is in a profile.
*/
@UserHandleAware(
requiresAnyOfPermissionsIfNotCallerProfileGroup = {
@@ -5244,7 +5283,7 @@
/**
* Returns the parent of the profile which this method is called from
- * or null if called from a user that is not a profile.
+ * or null if it has no parent (e.g. if it is not a profile).
*
* @hide
*/
@@ -5266,7 +5305,7 @@
*
* @param user the handle of the user profile
*
- * @return the parent of the user or {@code null} if the user is not profile
+ * @return the parent of the user or {@code null} if the user has no parent
*
* @hide
*/
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index c4521c0..86f03cd 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -34,3 +34,17 @@
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"
+}
+
+flag {
+ name: "battery_saver_supported_check_api"
+ namespace: "backstage_power"
+ description: "Guards a new API in PowerManager to check if battery saver is supported or not."
+ bug: "305067031"
+}
\ No newline at end of file
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 369a193..3ecf74e 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,20 +134,20 @@
@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 ee387e7..2d1802a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1589,28 +1589,64 @@
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 @@
/** {@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 3f06a91..0798f65 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -35,3 +35,10 @@
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 f0906b1..a2f1ce1e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11653,6 +11653,15 @@
"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 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/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 800149c..94eca3d 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -12,6 +12,7 @@
namespace: "hardware_backed_security"
description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
bug: "296464083"
+ is_fixed_read_only: true
}
flag {
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index a29bf7a..1afe8d9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -1401,6 +1401,7 @@
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
parcel.writeInt(mEligibleReason);
+ parcel.writeTypedObject(mAuthenticationExtras, flags);
}
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1436,6 +1437,7 @@
android.content.IntentSender.class);
final String datasetId = parcel.readString();
final int eligibleReason = parcel.readInt();
+ final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
@@ -1480,6 +1482,7 @@
fieldDialogPresentation);
}
builder.setAuthentication(authentication);
+ builder.setAuthenticationExtras(authenticationExtras);
builder.setId(datasetId);
Dataset dataset = builder.build();
dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 2931435..52d4d47 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -7,3 +7,11 @@
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 db97d4f..dfb1361 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -263,5 +263,12 @@
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 6a82f6d..875031f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -306,6 +306,7 @@
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 @@
}
@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 @@
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 ccf8b67..13b6a9a 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -19,6 +19,7 @@
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.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 @@
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 5cf245d..420dac1 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 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 32a93ee..16a6dbe 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -19,6 +19,7 @@
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.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
+import android.service.voice.flags.Flags;
import java.io.PrintWriter;
@@ -244,6 +246,19 @@
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/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
index 91e34dc..916fa36b 100644
--- a/core/java/android/service/voice/HotwordTrainingAudio.java
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -23,6 +24,7 @@
import android.media.AudioFormat;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import com.android.internal.util.DataClass;
@@ -33,6 +35,7 @@
*
* @hide
*/
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@DataClass(
genConstructor = false,
genBuilder = true,
@@ -65,12 +68,12 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
- */
+ * <p> Returns -1 if unset. */
@NonNull
private final int mAudioType;
private static int defaultAudioType() {
- return 0;
+ return -1;
}
/**
@@ -152,6 +155,7 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull int getAudioType() {
@@ -274,6 +278,7 @@
/**
* A builder for {@link HotwordTrainingAudio}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder extends BaseBuilder {
@@ -318,6 +323,7 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setAudioType(@NonNull int value) {
@@ -368,7 +374,7 @@
}
@DataClass.Generated(
- time = 1694193905346L,
+ time = 1697827049629L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
index 31aeb9c..aa6dab3 100644
--- a/core/java/android/service/voice/HotwordTrainingData.java
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -16,10 +16,12 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import android.text.TextUtils;
import com.android.internal.util.DataClass;
@@ -47,6 +49,7 @@
genParcelable = true,
genToString = true)
@SystemApi
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
public final class HotwordTrainingData implements Parcelable {
/** Max size for hotword training data in bytes. */
public static int getMaxTrainingDataBytes() {
@@ -63,11 +66,11 @@
}
/** App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset. */
+ * <p> Returns -1 if unset. */
private final int mTimeoutStage;
private static int defaultTimeoutStage() {
- return 0;
+ return -1;
}
private void onConstructed() {
@@ -120,7 +123,7 @@
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public int getTimeoutStage() {
@@ -218,6 +221,7 @@
/**
* A builder for {@link HotwordTrainingData}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder {
@@ -251,7 +255,7 @@
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setTimeoutStage(int value) {
@@ -287,7 +291,7 @@
}
@DataClass.Generated(
- time = 1696092128091L,
+ time = 1697826948280L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final int mTimeoutStage\npublic static int getMaxTrainingDataBytes()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
diff --git a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
new file mode 100644
index 0000000..7eb5280
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 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() {
+ LocalDate utcDate = LocalDate.now(ZoneOffset.UTC);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ String currentDate = utcDate.format(formatter);
+
+ 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 c6b10ff..a9c6af79 100644
--- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
+++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
@@ -18,6 +18,7 @@
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 @@
* 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 fab830a..6226772 100644
--- a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl
+++ b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl
@@ -20,6 +20,7 @@
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 @@
*/
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 f1bc792..2c68fae 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -18,6 +18,7 @@
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 @@
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 @@
}
@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 b5448d4..91de894 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -369,6 +369,12 @@
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 d280621..42203d4 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -18,6 +18,7 @@
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.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 @@
}
}
+ /** 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/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 65a1da6..4c81888 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -190,7 +190,8 @@
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
boolean useFallbackLineSpacing) {
return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
- ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */);
+ ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
+ null /* minimumFontMetrics */);
}
/** @hide */
@@ -199,7 +200,8 @@
@NonNull Alignment align, float spacingMultiplier, float spacingAmount,
@NonNull BoringLayout.Metrics metrics, boolean includePad,
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
- boolean useFallbackLineSpacing, boolean useBoundsForWidth) {
+ boolean useFallbackLineSpacing, boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -270,7 +272,8 @@
spacingAdd, includePad, false /* fallbackLineSpacing */,
outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
- null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
+ null);
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
@@ -343,7 +346,7 @@
ellipsizedWidth, ellipsize, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
+ LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
}
/** @hide */
@@ -359,12 +362,13 @@
int ellipsizedWidth,
TextUtils.TruncateAt ellipsize,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
this(text, paint, width, align, TextDirectionHeuristics.LTR,
spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, useBoundsForWidth);
+ LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
}
/* package */ BoringLayout(
@@ -387,12 +391,13 @@
int justificationMode,
LineBreakConfig lineBreakConfig,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
hyphenationFrequency, leftIndents, rightIndents, justificationMode,
- lineBreakConfig, useBoundsForWidth);
+ lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
boolean trust;
@@ -548,6 +553,15 @@
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
+ return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+ @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
final int textLength = text.length();
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
@@ -570,6 +584,19 @@
fm.reset();
}
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics == null) {
+ paint.getFontMetricsIntForLocale(fm);
+ } else {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than
+ // descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 46fa501..0421d5a 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -27,14 +27,6 @@
* @hide
*/
public class ClientFlags {
-
- /**
- * @see Flags#deprecateFontsXml()
- */
- public static boolean deprecateFontsXml() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
- }
-
/**
* @see Flags#noBreakNoHyphenationSpan()
*/
@@ -55,4 +47,11 @@
public static boolean useBoundsForWidth() {
return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
}
+
+ /**
+ * @see Flags#fixLineHeightForLocale()
+ */
+ public static boolean fixLineHeightForLocale() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
+ }
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index a0cd074..7b9cb6a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -315,6 +316,43 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link DynamicLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this method.
@@ -347,6 +385,7 @@
private int mEllipsizedWidth;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -422,7 +461,7 @@
false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, justificationMode,
- lineBreakConfig, false /* useBoundsForWidth */);
+ lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */);
final Builder b = Builder.obtain(base, paint, width)
.setAlignment(align)
@@ -448,7 +487,7 @@
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, b.mJustificationMode,
- b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics);
mDisplay = b.mDisplay;
mIncludePad = b.mIncludePad;
@@ -476,6 +515,7 @@
mBase = b.mBase;
mFallbackLineSpacing = b.mFallbackLineSpacing;
mUseBoundsForWidth = b.mUseBoundsForWidth;
+ mMinimumFontMetrics = b.mMinimumFontMetrics;
if (b.mEllipsize != null) {
mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
mEllipsizedWidth = b.mEllipsizedWidth;
@@ -672,6 +712,7 @@
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
.setCalculateBounds(true);
reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
@@ -1324,6 +1365,7 @@
private Rect mTempRect = new Rect();
private boolean mUseBoundsForWidth;
+ @Nullable Paint.FontMetrics mMinimumFontMetrics;
@UnsupportedAppUsage
private static StaticLayout sStaticLayout = null;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4f4dea7..47c29d9 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -287,7 +288,7 @@
this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null,
- JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null);
}
/**
@@ -336,7 +337,8 @@
int[] rightIndents,
int justificationMode,
LineBreakConfig lineBreakConfig,
- boolean useBoundsForWidth
+ boolean useBoundsForWidth,
+ Paint.FontMetrics minimumFontMetrics
) {
if (width < 0)
@@ -371,6 +373,7 @@
mJustificationMode = justificationMode;
mLineBreakConfig = lineBreakConfig;
mUseBoundsForWidth = useBoundsForWidth;
+ mMinimumFontMetrics = minimumFontMetrics;
}
/**
@@ -3332,6 +3335,7 @@
private int mJustificationMode;
private LineBreakConfig mLineBreakConfig;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
/** @hide */
@IntDef(prefix = { "DIR_" }, value = {
@@ -3787,12 +3791,48 @@
return this;
}
+ /**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left it as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
private BoringLayout.Metrics isBoring() {
if (mStart != 0 || mEnd != mText.length()) { // BoringLayout only support entire text.
return null;
}
BoringLayout.Metrics metrics = BoringLayout.isBoring(mText, mPaint, mTextDir,
- mFallbackLineSpacing, null);
+ mFallbackLineSpacing, mMinimumFontMetrics, null);
if (metrics == null) {
return null;
}
@@ -3833,7 +3873,8 @@
mText, mPaint, mWidth, mAlignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
- mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth);
+ mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
@@ -3858,6 +3899,7 @@
private int mJustificationMode = JUSTIFICATION_MODE_NONE;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private Paint.FontMetrics mMinimumFontMetrics;
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -4164,4 +4206,22 @@
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
}
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 517ae4f..5f6a9bd 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -457,12 +457,21 @@
} 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 @@
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 @@
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/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 01279ce..77e616b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -460,6 +461,43 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -520,6 +558,7 @@
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
private boolean mCalculateBounds;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -550,7 +589,8 @@
null, // rightIndents
JUSTIFICATION_MODE_NONE,
null, // lineBreakConfig,
- false // useBoundsForWidth
+ false, // useBoundsForWidth
+ null // minimumFontMetrics
);
mColumns = COLUMNS_ELLIPSIZE;
@@ -627,7 +667,8 @@
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
- b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
+ b.mMinimumFontMetrics);
mColumns = columnSize;
if (b.mEllipsize != null) {
@@ -711,6 +752,35 @@
indents = null;
}
+ int defaultTop;
+ int defaultAscent;
+ int defaultDescent;
+ int defaultBottom;
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (b.mMinimumFontMetrics != null) {
+ defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+ defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+ defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+ defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
+ } else {
+ paint.getFontMetricsIntForLocale(fm);
+ defaultTop = fm.top;
+ defaultAscent = fm.ascent;
+ defaultDescent = fm.descent;
+ defaultBottom = fm.bottom;
+ }
+
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
+ defaultTop = Math.min(defaultTop, defaultAscent);
+ defaultBottom = Math.max(defaultBottom, defaultDescent);
+ } else {
+ defaultTop = 0;
+ defaultAscent = 0;
+ defaultDescent = 0;
+ defaultBottom = 0;
+ }
+
final LineBreaker lineBreaker = new LineBreaker.Builder()
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
@@ -889,7 +959,10 @@
// measuring
int here = paraStart;
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
int fmCacheIndex = 0;
int spanEndCacheIndex = 0;
int breakIndex = 0;
@@ -982,7 +1055,15 @@
&& mLineCount < mMaximumVisibleLineCount) {
final MeasuredParagraph measuredPara =
MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- paint.getFontMetricsInt(fm);
+ if (ClientFlags.fixLineHeightForLocale()) {
+ fm.top = defaultTop;
+ fm.ascent = defaultAscent;
+ fm.descent = defaultDescent;
+ fm.bottom = defaultBottom;
+ } else {
+ paint.getFontMetricsInt(fm);
+ }
+
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
fm.top, fm.bottom,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9edf298..2466386 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,10 +55,10 @@
* List of text flags to be transferred to the application process.
*/
public static final String[] TEXT_ACONFIGS_FLAGS = {
- Flags.FLAG_DEPRECATE_FONTS_XML,
Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
+ Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
};
/**
@@ -67,10 +67,10 @@
* The order must be the same to the TEXT_ACONFIG_FLAGS.
*/
public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- Flags.deprecateFontsXml(),
Flags.noBreakNoHyphenationSpan(),
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
+ Flags.fixLineHeightForLocale(),
};
/**
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
deleted file mode 100644
index 52fe883..0000000
--- a/core/java/android/text/flags/custom_locale_fallback.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "custom_locale_fallback"
- namespace: "text"
- description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
- is_fixed_read_only: true
- bug: "278768958"
-}
diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
deleted file mode 100644
index 5362138..0000000
--- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "deprecate_fonts_xml"
- namespace: "text"
- description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
- # Make read only, as it could be used before the Settings provider is initialized.
- is_fixed_read_only: true
- bug: "281769620"
-}
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
deleted file mode 100644
index b0aa72a..0000000
--- a/core/java/android/text/flags/fix_double_underline.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_double_underline"
- namespace: "text"
- description: "Feature flag for fixing double underline because of the multiple font used in the single line."
- bug: "297336724"
-}
diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig
deleted file mode 100644
index 8696bfa..0000000
--- a/core/java/android/text/flags/fix_line_height_for_locale.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_line_height_for_locale"
- namespace: "text"
- description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
- bug: "303326708"
-}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
new file mode 100644
index 0000000..43c38f3
--- /dev/null
+++ b/core/java/android/text/flags/flags.aconfig
@@ -0,0 +1,77 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "vendor_custom_locale_fallback"
+ namespace: "text"
+ description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+ is_fixed_read_only: true
+ bug: "278768958"
+}
+
+flag {
+ name: "new_fonts_fallback_xml"
+ namespace: "text"
+ description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+ # Make read only, as it could be used before the Settings provider is initialized.
+ is_fixed_read_only: true
+ bug: "281769620"
+}
+
+flag {
+ name: "fix_double_underline"
+ namespace: "text"
+ description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+ bug: "297336724"
+}
+
+flag {
+ name: "fix_line_height_for_locale"
+ namespace: "text"
+ description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+ bug: "303326708"
+}
+
+flag {
+ name: "no_break_no_hyphenation_span"
+ namespace: "text"
+ description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+ bug: "283193586"
+}
+
+flag {
+ name: "use_optimized_boottime_font_loading"
+ namespace: "text"
+ description: "Feature flag ensuring that font is loaded once and asynchronously."
+ # Make read only, as font loading is in the critical boot path which happens before the read-write
+ # flags propagate to the device.
+ is_fixed_read_only: true
+ bug: "304406888"
+}
+
+flag {
+ name: "phrase_strict_fallback"
+ namespace: "text"
+ description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+ bug: "281970875"
+}
+
+flag {
+ name: "use_bounds_for_width"
+ namespace: "text"
+ 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/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
deleted file mode 100644
index 60f1e88..0000000
--- a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "no_break_no_hyphenation_span"
- namespace: "text"
- description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
- bug: "283193586"
-}
diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig
deleted file mode 100644
index 0e4a69f..0000000
--- a/core/java/android/text/flags/optimized_font_loading.aconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_optimized_boottime_font_loading"
- namespace: "text"
- description: "Feature flag ensuring that font is loaded once and asynchronously."
- # Make read only, as font loading is in the critical boot path which happens before the read-write
- # flags propagate to the device.
- is_fixed_read_only: true
- bug: "304406888"
-}
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
deleted file mode 100644
index c67a21b..0000000
--- a/core/java/android/text/flags/phrase_strict_fallback.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "phrase_strict_fallback"
- namespace: "text"
- description: "Feature flag for automatic fallback from phrase based line break to strict line break."
- bug: "281970875"
-}
diff --git a/core/java/android/text/flags/use_bounds_for_width.aconfig b/core/java/android/text/flags/use_bounds_for_width.aconfig
deleted file mode 100644
index d89d5f4..0000000
--- a/core/java/android/text/flags/use_bounds_for_width.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_bounds_for_width"
- namespace: "text"
- description: "Feature flag for preventing horizontal clipping."
- bug: "63938206"
-}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 17c7fcc..07dd882 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -18,8 +18,10 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1468,17 +1470,18 @@
}
/**
- * Returns null if it's virtual display.
- * @hide
+ * Returns the {@link OverlayProperties} of the display.
*/
- @Nullable
+ @FlaggedApi(FLAG_OVERLAYPROPERTIES_CLASS_API)
+ @NonNull
public OverlayProperties getOverlaySupport() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.type != TYPE_VIRTUAL) {
+ if (mDisplayInfo.type == TYPE_INTERNAL
+ || mDisplayInfo.type == TYPE_EXTERNAL) {
return mGlobal.getOverlaySupport();
}
- return null;
+ return OverlayProperties.getDefault();
}
}
@@ -2091,7 +2094,8 @@
private final int mModeId;
private final int mWidth;
private final int mHeight;
- private final float mRefreshRate;
+ private final float mPeakRefreshRate;
+ private final float mVsyncRate;
@NonNull
private final float[] mAlternativeRefreshRates;
@NonNull
@@ -2103,7 +2107,15 @@
*/
@TestApi
public Mode(int width, int height, float refreshRate) {
- this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
+ this(INVALID_MODE_ID, width, height, refreshRate, refreshRate, new float[0],
+ new int[0]);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int width, int height, float refreshRate, float vsyncRate) {
+ this(INVALID_MODE_ID, width, height, refreshRate, vsyncRate, new float[0], new int[0]);
}
/**
@@ -2111,18 +2123,29 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Mode(int modeId, int width, int height, float refreshRate) {
- this(modeId, width, height, refreshRate, new float[0], new int[0]);
+ this(modeId, width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
/**
* @hide
*/
public Mode(int modeId, int width, int height, float refreshRate,
+ float[] alternativeRefreshRates,
+ @HdrCapabilities.HdrType int[] supportedHdrTypes) {
+ this(modeId, width, height, refreshRate, refreshRate, alternativeRefreshRates,
+ supportedHdrTypes);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
mModeId = modeId;
mWidth = width;
mHeight = height;
- mRefreshRate = refreshRate;
+ mPeakRefreshRate = refreshRate;
+ mVsyncRate = vsyncRate;
mAlternativeRefreshRates =
Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
Arrays.sort(mAlternativeRefreshRates);
@@ -2173,7 +2196,17 @@
* Returns the refresh rate in frames per second.
*/
public float getRefreshRate() {
- return mRefreshRate;
+ return mPeakRefreshRate;
+ }
+
+ /**
+ * Returns the vsync rate in frames per second.
+ * The physical vsync rate may be higher than the refresh rate, as the refresh rate may be
+ * constrained by the system.
+ * @hide
+ */
+ public float getVsyncRate() {
+ return mVsyncRate;
}
/**
@@ -2219,7 +2252,7 @@
public boolean matches(int width, int height, float refreshRate) {
return mWidth == width &&
mHeight == height &&
- Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+ Float.floatToIntBits(mPeakRefreshRate) == Float.floatToIntBits(refreshRate);
}
/**
@@ -2232,9 +2265,9 @@
*
* @hide
*/
- public boolean matchesIfValid(int width, int height, float refreshRate) {
+ public boolean matchesIfValid(int width, int height, float peakRefreshRate) {
if (!isWidthValid(width) && !isHeightValid(height)
- && !isRefreshRateValid(refreshRate)) {
+ && !isRefreshRateValid(peakRefreshRate)) {
return false;
}
if (isWidthValid(width) != isHeightValid(height)) {
@@ -2242,8 +2275,9 @@
}
return (!isWidthValid(width) || mWidth == width)
&& (!isHeightValid(height) || mHeight == height)
- && (!isRefreshRateValid(refreshRate)
- || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+ && (!isRefreshRateValid(peakRefreshRate)
+ || Float.floatToIntBits(mPeakRefreshRate)
+ == Float.floatToIntBits(peakRefreshRate));
}
/**
@@ -2262,7 +2296,7 @@
* @hide
*/
public boolean isRefreshRateSet() {
- return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+ return mPeakRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
}
/**
@@ -2283,7 +2317,8 @@
return false;
}
Mode that = (Mode) other;
- return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
+ return mModeId == that.mModeId
+ && matches(that.mWidth, that.mHeight, that.mPeakRefreshRate)
&& Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
&& Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
}
@@ -2294,7 +2329,8 @@
hash = hash * 17 + mModeId;
hash = hash * 17 + mWidth;
hash = hash * 17 + mHeight;
- hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mVsyncRate);
hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
return hash;
@@ -2306,7 +2342,8 @@
.append("id=").append(mModeId)
.append(", width=").append(mWidth)
.append(", height=").append(mHeight)
- .append(", fps=").append(mRefreshRate)
+ .append(", fps=").append(mPeakRefreshRate)
+ .append(", vsync=").append(mVsyncRate)
.append(", alternativeRefreshRates=")
.append(Arrays.toString(mAlternativeRefreshRates))
.append(", supportedHdrTypes=")
@@ -2321,8 +2358,8 @@
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
- in.createIntArray());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+ in.createFloatArray(), in.createIntArray());
}
@Override
@@ -2330,7 +2367,8 @@
out.writeInt(mModeId);
out.writeInt(mWidth);
out.writeInt(mHeight);
- out.writeFloat(mRefreshRate);
+ out.writeFloat(mPeakRefreshRate);
+ out.writeFloat(mVsyncRate);
out.writeFloatArray(mAlternativeRefreshRates);
out.writeIntArray(mSupportedHdrTypes);
}
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 1310b0c..6b354a0 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,16 +16,10 @@
package android.view;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.view.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
*
@@ -36,16 +30,12 @@
* methods in this class. To check if your input device ID, source, and motion axis are valid for
* haptic feedback, you can use the
* {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
private static final String TAG = "HapticScrollFeedbackProvider";
- /** @hide */
- @IntDef(value = {MotionEvent.AXIS_SCROLL})
- @Retention(RetentionPolicy.SOURCE)
- public @interface HapticScrollFeedbackAxis {}
-
private static final int TICK_INTERVAL_NO_TICK = 0;
private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false;
@@ -89,8 +79,7 @@
}
@Override
- public void onScrollProgress(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
+ public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -117,8 +106,7 @@
}
@Override
- public void onScrollLimit(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
+ public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -135,7 +123,7 @@
}
@Override
- public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
+ public void onSnapToItem(int inputDeviceId, int source, int axis) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 1ec7c41..17a3a12 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_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 @@
}
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/InsetsController.java b/core/java/android/view/InsetsController.java
index fb24211..90663c7 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1283,7 +1283,6 @@
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
final boolean monitoredAnimation =
animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1295,12 +1294,15 @@
ImeTracker.forLatency().onHideCancelled(statsToken,
PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
}
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
}
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
+ " while an existing " + Type.toString(mTypesBeingCancelled)
+ " is being cancelled.");
}
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
@@ -1309,8 +1311,6 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 99a7fe5..aad3bf2 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -20,11 +20,9 @@
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 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 @@
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 @@
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 @@
protected LayoutInflater(Context context) {
StrictMode.assertConfigurationContext(context, "LayoutInflater");
mContext = context;
- initPrecompiledViews();
}
/**
@@ -277,7 +257,6 @@
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
- initPrecompiledViews();
}
/**
@@ -419,57 +398,6 @@
}
}
- 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 @@
+ 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 @@
}
}
- 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 @@
* 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 @@
+ "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);
+ final XmlResourceParser childParser = context.getResources().getLayout(layout);
+ try {
+ final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
- try {
- final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
-
- while ((type = childParser.next()) != XmlPullParser.START_TAG &&
- type != XmlPullParser.END_DOCUMENT) {
- // Empty.
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new InflateException(getParserStateDescription(context, childAttrs)
- + ": No start tag found!");
- }
-
- final String childName = childParser.getName();
-
- 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);
-
- // Inflate all children.
- rInflateChildren(childParser, view, childAttrs, true);
-
- if (id != View.NO_ID) {
- view.setId(id);
- }
-
- switch (visibility) {
- case 0:
- view.setVisibility(View.VISIBLE);
- break;
- case 1:
- view.setVisibility(View.INVISIBLE);
- break;
- case 2:
- view.setVisibility(View.GONE);
- break;
- }
-
- group.addView(view);
- }
- } finally {
- childParser.close();
+ while ((type = childParser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Empty.
}
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(getParserStateDescription(context, childAttrs)
+ + ": No start tag found!");
+ }
+
+ final String childName = childParser.getName();
+
+ 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);
+
+ // Inflate all children.
+ rInflateChildren(childParser, view, childAttrs, true);
+
+ if (id != View.NO_ID) {
+ view.setId(id);
+ }
+
+ switch (visibility) {
+ case 0:
+ view.setVisibility(View.VISIBLE);
+ break;
+ case 1:
+ view.setVisibility(View.INVISIBLE);
+ break;
+ case 2:
+ view.setVisibility(View.GONE);
+ break;
+ }
+
+ group.addView(view);
+ }
+ } finally {
+ childParser.close();
}
+
LayoutInflater.consumeChildElements(parser);
}
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 0ba4148..8a44d4f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,6 +17,7 @@
package android.view;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.view.flags.Flags;
/**
@@ -62,23 +63,37 @@
* </ul>
*
* <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
- * supported for scroll feedback. If you are implementing this interface, provide clear
- * documentation in your implementation class about which input device source and motion axis are
- * supported for your specific implementation. If you are using one of the implementations of this
- * interface, please refer to the documentation of the implementation for details on which input
- * device source and axis are supported.
+ * supported for scroll feedback; the implementation may choose to provide no feedback for some
+ * valid input device source and motion axis arguments.
*/
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public interface ScrollFeedbackProvider {
+
+ /**
+ * Creates a {@link ScrollFeedbackProvider} implementation for this device.
+ *
+ * <p>Use a feedback provider created by this method, unless you intend to use your custom
+ * scroll feedback providing logic. This allows your use cases to generate scroll feedback that
+ * is consistent with the rest of the use cases on the device.
+ *
+ * @param view the {@link View} for which to provide scroll feedback.
+ * @return the default {@link ScrollFeedbackProvider} implementation for the device.
+ */
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
+ @NonNull
+ static ScrollFeedbackProvider createProvider(@NonNull View view) {
+ return new HapticScrollFeedbackProvider(view);
+ }
+
/**
* Call this when the view has snapped to an item.
*
- *
* @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
* the snap.
* @param source the input source of the motion causing the snap.
* @param axis the axis of {@code event} that caused the item to snap.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onSnapToItem(int inputDeviceId, int source, int axis);
/**
@@ -99,6 +114,7 @@
* "start" for some views may be at the bottom of a scrolling list, while it may
* be at the top of scrolling list for others.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);
/**
@@ -122,5 +138,6 @@
* @param axis the axis of {@code event} that caused scroll progress.
* @param deltaInPixels the amount of scroll progress, in pixels.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 42a0c9a..e22207c 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1796,7 +1796,7 @@
public float yDpi;
// Some modes have peak refresh rate lower than the panel vsync rate.
- public float refreshRate;
+ public float peakRefreshRate;
// Fixed rate of vsync deadlines for the panel.
// This can be higher then the peak refresh rate for some panel technologies
// See: VrrConfig.aidl
@@ -1820,7 +1820,7 @@
+ ", height=" + height
+ ", xDpi=" + xDpi
+ ", yDpi=" + yDpi
- + ", refreshRate=" + refreshRate
+ + ", peakRefreshRate=" + peakRefreshRate
+ ", vsyncRate=" + vsyncRate
+ ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+ ", presentationDeadlineNanos=" + presentationDeadlineNanos
@@ -1838,7 +1838,7 @@
&& height == that.height
&& Float.compare(that.xDpi, xDpi) == 0
&& Float.compare(that.yDpi, yDpi) == 0
- && Float.compare(that.refreshRate, refreshRate) == 0
+ && Float.compare(that.peakRefreshRate, peakRefreshRate) == 0
&& Float.compare(that.vsyncRate, vsyncRate) == 0
&& appVsyncOffsetNanos == that.appVsyncOffsetNanos
&& presentationDeadlineNanos == that.presentationDeadlineNanos
@@ -1848,7 +1848,7 @@
@Override
public int hashCode() {
- return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate,
+ return Objects.hash(id, width, height, xDpi, yDpi, peakRefreshRate, vsyncRate,
appVsyncOffsetNanos, presentationDeadlineNanos, group,
Arrays.hashCode(supportedHdrTypes));
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2cf5d5d..ec96167 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -1272,12 +1271,10 @@
* @see InputDevice#getMotionRanges()
* @see InputDevice#getMotionRange(int)
* @see InputDevice#getMotionRange(int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public boolean isHapticScrollFeedbackEnabled(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;
if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
@@ -1318,12 +1315,10 @@
* returns {@code Integer.MAX_VALUE}.
*
* @see #isHapticScrollFeedbackEnabled(int, int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public int getHapticScrollFeedbackTickInterval(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
return NO_HAPTIC_SCROLL_TICK_INTERVAL;
}
@@ -1343,9 +1338,6 @@
* Checks if the View-based haptic scroll feedback implementation is enabled for
* {@link InputDevice#SOURCE_ROTARY_ENCODER}s.
*
- * <p>If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be
- * muted for rotary encoders in favor of View's scroll haptics implementation.
- *
* @hide
*/
public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4a10cab..4da02f9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1020,8 +1020,7 @@
mDisplay = display;
mBasePackageName = context.getBasePackageName();
final String name = DisplayProperties.debug_vri_package().orElse(null);
- // TODO: b/306170135 - return to using textutils check on package name.
- mExtraDisplayListenerLogging = true;
+ mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -4751,7 +4750,7 @@
mFullRedrawNeeded = false;
mIsDrawing = true;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, mTag + "-draw");
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw-" + mTag);
addFrameCommitCallbackIfNeeded();
@@ -11613,7 +11612,14 @@
Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer);
}
- surfaceSyncGroup.addTransaction(mPendingTransaction);
+ final Transaction t;
+ if (mHasPendingTransactions) {
+ t = new Transaction();
+ t.merge(mPendingTransaction);
+ } else {
+ t = null;
+ }
+
mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
@@ -11626,6 +11632,9 @@
"Received frameDrawingCallback syncResult=" + syncResult + " frameNum="
+ frame + ".");
}
+ if (t != null) {
+ mergeWithNextTransaction(t, frame);
+ }
// If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
// SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 32256b9..b3359b7 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,8 +16,8 @@
package android.view.animation;
-import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
-import static android.view.flags.Flags.expectedPresentationTimeApi;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY;
+import static android.view.flags.Flags.expectedPresentationTimeReadOnly;
import android.annotation.AnimRes;
import android.annotation.FlaggedApi;
@@ -67,6 +67,11 @@
@Overridable
public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
+ private static boolean sExpectedPresentationTimeFlagValue;
+ static {
+ sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
+ }
+
private static class AnimationState {
boolean animationClockLocked;
long currentVsyncTimeMillis;
@@ -108,12 +113,12 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
AnimationState state = sAnimationState.get();
state.animationClockLocked = true;
state.currentVsyncTimeMillis = vsyncMillis;
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
}
}
@@ -158,9 +163,9 @@
* @return the expected presentation time of a frame in the
* {@link System#nanoTime()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeNanos() {
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
return SystemClock.uptimeMillis();
}
@@ -176,7 +181,7 @@
* @return the expected presentation time of a frame in the
* {@link SystemClock#uptimeMillis()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeMillis() {
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index a40ff64..96574f5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3392,7 +3392,7 @@
return false;
}
for (String hint : hints) {
- if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+ if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
return true;
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 42b3e38..57011e8 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -364,14 +364,6 @@
"enable_content_protection_receiver";
/**
- * Sets the size of the app blocklist for the content protection flow.
- *
- * @hide
- */
- public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
- "content_protection_apps_blocklist_size";
-
- /**
* Sets the size of the in-memory ring buffer for the content protection flow.
*
* @hide
@@ -440,8 +432,6 @@
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
/** @hide */
- public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
- /** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
/** @hide */
public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index fd96890..2b08eeb 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -22,6 +22,14 @@
}
flag {
+ name: "expected_presentation_time_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for using expected presentation time of the Choreographer"
+ bug: "278730197"
+ is_fixed_read_only: true
+}
+
+flag {
name: "set_frame_rate_callback"
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f9d8b08..1bc7353 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -172,7 +172,6 @@
PHASE_CLIENT_HANDLE_HIDE_INSETS,
PHASE_CLIENT_APPLY_ANIMATION,
PHASE_CLIENT_CONTROL_ANIMATION,
- PHASE_CLIENT_DISABLED_USER_ANIMATION,
PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
PHASE_CLIENT_REQUEST_IME_SHOW,
@@ -292,9 +291,6 @@
/** Started the IME window insets show animation. */
int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
- /** Checked that the IME is controllable. */
- int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
-
/** Collecting insets source controls. */
int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 7eee33f..9f0b31b 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -770,7 +770,7 @@
}
/**
- * Set the font variation settings. Returns null if no variation is specified.
+ * Set the font variation settings. Set {@code null} if no variation is specified.
*
* @see Paint#getFontVariationSettings()
*/
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a0628c4..6da6a64 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.R;
@@ -865,6 +866,7 @@
private final boolean mUseTextPaddingForUiTranslation;
private boolean mUseBoundsForWidth;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
@@ -4901,6 +4903,58 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
+ * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
+ * the line spacing for Japanese is reserved if the TextView contains English text. If the
+ * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
+ * metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
+ * obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see #getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ }
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
+
+ /**
* @return whether fallback line spacing is enabled, {@code true} by default
*
* @see #setFallbackLineSpacing(boolean)
@@ -10683,7 +10737,8 @@
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(),
+ mMinimumFontMetrics, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -10732,7 +10787,8 @@
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10796,12 +10852,13 @@
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
.setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
- .setEllipsizedWidth(ellipsisWidth);
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10815,7 +10872,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, null, wantWidth,
isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10829,7 +10886,8 @@
wantWidth,
null,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
if (useSaved) {
@@ -10841,7 +10899,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10855,7 +10913,8 @@
ellipsisWidth,
effectiveEllipsize,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
}
@@ -10874,7 +10933,8 @@
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -11002,7 +11062,7 @@
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -11042,7 +11102,8 @@
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+ mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -11254,7 +11315,8 @@
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 841354a..e6eeca4 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -35,17 +35,21 @@
private final boolean mVisible;
+ private final boolean mHasDirectActivity;
+
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
- boolean visible) {
+ boolean visible, boolean hasDirectActivity) {
mConfiguration.setTo(configuration);
mDisplayId = displayId;
mVisible = visible;
+ mHasDirectActivity = hasDirectActivity;
}
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
mVisible = info.mVisible;
+ mHasDirectActivity = info.mHasDirectActivity;
}
/** The {@link Configuration} of the parent Task */
@@ -68,6 +72,14 @@
}
/**
+ * Whether the parent Task has any direct child activity, which is not embedded in any
+ * TaskFragment, or not
+ */
+ public boolean hasDirectActivity() {
+ return mHasDirectActivity;
+ }
+
+ /**
* Returns {@code true} if the parameters which are important for task fragment
* organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}.
* Note that this method is usually called with
@@ -80,7 +92,7 @@
return false;
}
return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
- && mVisible == that.mVisible;
+ && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity;
}
@WindowConfiguration.WindowingMode
@@ -94,6 +106,7 @@
+ "config=" + mConfiguration
+ ", displayId=" + mDisplayId
+ ", visible=" + mVisible
+ + ", hasDirectActivity=" + mHasDirectActivity
+ "}";
}
@@ -114,7 +127,8 @@
final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
return mConfiguration.equals(that.mConfiguration)
&& mDisplayId == that.mDisplayId
- && mVisible == that.mVisible;
+ && mVisible == that.mVisible
+ && mHasDirectActivity == that.mHasDirectActivity;
}
@Override
@@ -122,6 +136,7 @@
int result = mConfiguration.hashCode();
result = 31 * result + mDisplayId;
result = 31 * result + (mVisible ? 1 : 0);
+ result = 31 * result + (mHasDirectActivity ? 1 : 0);
return result;
}
@@ -130,12 +145,14 @@
mConfiguration.writeToParcel(dest, flags);
dest.writeInt(mDisplayId);
dest.writeBoolean(mVisible);
+ dest.writeBoolean(mHasDirectActivity);
}
private TaskFragmentParentInfo(Parcel in) {
mConfiguration.readFromParcel(in);
mDisplayId = in.readInt();
mVisible = in.readBoolean();
+ mHasDirectActivity = in.readBoolean();
}
public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c2b5196..d9b5b2d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -804,7 +804,7 @@
* 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 0cbfcc5..c81c9ec 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -129,7 +129,6 @@
@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/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 79b3b4f..4705dc5 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -23,7 +23,7 @@
}
flag {
- name: "dimmer_refactor"
+ name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
bug: "295291019"
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ba87caa..a65877c 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.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 @@
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 314ed69..68e2b48 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -359,6 +359,12 @@
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/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index f77e962..65a2f4b 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -46,4 +46,5 @@
in @nullable ImeTracker.Token statsToken);
void onStylusHandwritingReady(int requestId, int pid);
void resetStylusHandwriting(int requestId);
+ void switchKeyboardLayoutAsync(int direction);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 30ebbe2..792388d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -431,4 +431,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Calls {@link IInputMethodPrivilegedOperations#switchKeyboardLayoutAsync(int)}.
+ */
+ @AnyThread
+ public void switchKeyboardLayoutAsync(int direction) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.switchKeyboardLayoutAsync(direction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 7e9cef7..e6b036c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -58,6 +58,9 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
@@ -276,7 +279,11 @@
public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
- private static final int LAST_CUJ = CUJ_LAUNCHER_UNFOLD_ANIM;
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+ public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+ private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -370,6 +377,12 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
}
private static class InstanceHolder {
@@ -473,6 +486,9 @@
CUJ_IME_INSETS_HIDE_ANIMATION,
CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
CUJ_LAUNCHER_UNFOLD_ANIM,
+ CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ CUJ_PREDICTIVE_BACK_CROSS_TASK,
+ CUJ_PREDICTIVE_BACK_HOME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1108,6 +1124,12 @@
return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
case CUJ_LAUNCHER_UNFOLD_ANIM:
return "LAUNCHER_UNFOLD_ANIM";
+ case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+ return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+ case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+ return "PREDICTIVE_BACK_CROSS_TASK";
+ case CUJ_PREDICTIVE_BACK_HOME:
+ return "PREDICTIVE_BACK_HOME";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 0399430..7d78f29 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -603,6 +603,17 @@
}
/**
+ * Returns the monotonic clock time when the available battery history collection started.
+ */
+ public long getStartTime() {
+ if (!mHistoryFiles.isEmpty()) {
+ return mHistoryFiles.get(0).monotonicTimeMs;
+ } else {
+ return mHistoryBufferStartTime;
+ }
+ }
+
+ /**
* Start iterating history files and history buffer.
*
* @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from,
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index dc5055a..ecfed53 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -29,6 +29,8 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Maintains multidimensional multi-state stats. States could be something like on-battery (0,1),
@@ -52,13 +54,55 @@
public States(String name, boolean tracked, String... labels) {
mName = name;
- this.mTracked = tracked;
- this.mLabels = labels;
+ mTracked = tracked;
+ mLabels = labels;
}
public boolean isTracked() {
return mTracked;
}
+
+ public String getName() {
+ return mName;
+ }
+
+ public String[] getLabels() {
+ return mLabels;
+ }
+
+ /**
+ * Iterates over all combinations of tracked states and invokes <code>consumer</code>
+ * for each of them.
+ */
+ public static void forEachTrackedStateCombination(States[] states,
+ Consumer<int[]> consumer) {
+ forEachTrackedStateCombination(consumer, states, new int[states.length], 0);
+ }
+
+ /**
+ * Recursive function that does a depth-first traversal of the multi-dimensional
+ * state space. Each time the traversal reaches the end of the <code>states</code> array,
+ * <code>statesValues</code> contains a unique combination of values for all tracked states.
+ * For untracked states, the corresponding values are left as 0. The end result is
+ * that the <code>consumer</code> is invoked for every unique combination of tracked state
+ * values. For example, it may be a sequence of calls like screen-on/power-on,
+ * screen-on/power-off, screen-off/power-on, screen-off/power-off.
+ */
+ private static void forEachTrackedStateCombination(Consumer<int[]> consumer,
+ States[] states, int[] statesValues, int stateIndex) {
+ if (stateIndex < statesValues.length) {
+ if (!states[stateIndex].mTracked) {
+ forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1);
+ return;
+ }
+ for (int i = 0; i < states[stateIndex].mLabels.length; i++) {
+ statesValues[stateIndex] = i;
+ forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1);
+ }
+ return;
+ }
+ consumer.accept(statesValues);
+ }
}
/**
@@ -276,6 +320,13 @@
}
/**
+ * Updates the stats values for the provided combination of states.
+ */
+ public void setStats(int[] states, long[] values) {
+ mCounter.setValues(mFactory.getSerialState(states), values);
+ }
+
+ /**
* Resets the counters.
*/
public void reset() {
@@ -293,24 +344,27 @@
*/
public void writeXml(TypedXmlSerializer serializer) throws IOException {
long[] tmpArray = new long[mCounter.getArrayLength()];
- writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray);
+
+ try {
+ States.forEachTrackedStateCombination(mFactory.mStates,
+ states -> {
+ try {
+ writeXmlForStates(serializer, states, tmpArray);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof IOException) {
+ throw (IOException) e.getCause();
+ } else {
+ throw e;
+ }
+ }
}
- private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex,
- long[] values) throws IOException {
- if (stateIndex < states.length) {
- if (!mFactory.mStates[stateIndex].mTracked) {
- writeXmlAllStates(serializer, states, stateIndex + 1, values);
- return;
- }
-
- for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
- states[stateIndex] = i;
- writeXmlAllStates(serializer, states, stateIndex + 1, values);
- }
- return;
- }
-
+ private void writeXmlForStates(TypedXmlSerializer serializer, int[] states, long[] values)
+ throws IOException {
mCounter.getCounts(values, mFactory.getSerialState(states));
boolean nonZero = false;
for (long value : values) {
@@ -391,48 +445,33 @@
/**
* Prints the accumulated stats, one line of every combination of states that has data.
*/
- public void dump(PrintWriter pw) {
- long[] tmpArray = new long[mCounter.getArrayLength()];
- dumpAllStates(pw, new int[mFactory.mStates.length], 0, tmpArray);
- }
-
- private void dumpAllStates(PrintWriter pw, int[] states, int stateIndex, long[] values) {
- if (stateIndex < states.length) {
- if (!mFactory.mStates[stateIndex].mTracked) {
- dumpAllStates(pw, states, stateIndex + 1, values);
+ public void dump(PrintWriter pw, Function<long[], String> statsFormatter) {
+ long[] values = new long[mCounter.getArrayLength()];
+ States.forEachTrackedStateCombination(mFactory.mStates, states -> {
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
return;
}
- for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
- states[stateIndex] = i;
- dumpAllStates(pw, states, stateIndex + 1, values);
- }
- return;
- }
-
- mCounter.getCounts(values, mFactory.getSerialState(states));
- boolean nonZero = false;
- for (long value : values) {
- if (value != 0) {
- nonZero = true;
- break;
- }
- }
- if (!nonZero) {
- return;
- }
-
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < states.length; i++) {
- if (mFactory.mStates[i].mTracked) {
- if (sb.length() != 0) {
- sb.append(" ");
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked) {
+ if (sb.length() != 0) {
+ sb.append(" ");
+ }
+ sb.append(mFactory.mStates[i].mLabels[states[i]]);
}
- sb.append(mFactory.mStates[i].mLabels[states[i]]);
}
- }
- sb.append(" ");
- sb.append(Arrays.toString(values));
- pw.println(sb);
+ sb.append(" ");
+ sb.append(statsFormatter.apply(values));
+ pw.println(sb);
+ });
}
}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 503e689..2298cbd 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -44,7 +44,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.Locale;
/**
* Reports power consumption values for various device activities. Reads values from an XML file.
@@ -295,7 +294,7 @@
private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
- private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3;
+ public static final int POWER_BRACKETS_UNSPECIFIED = -1;
/**
* A map from Power Use Item to its power consumption.
@@ -361,7 +360,7 @@
}
initCpuClusters();
initCpuScalingPolicies();
- initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER);
+ initCpuPowerBrackets();
initDisplays();
initModem();
}
@@ -560,8 +559,7 @@
/**
* Parses or computes CPU power brackets: groups of states with similar power requirements.
*/
- @VisibleForTesting
- public void initCpuPowerBrackets(int defaultCpuPowerBracketNumber) {
+ private void initCpuPowerBrackets() {
boolean anyBracketsSpecified = false;
boolean allBracketsSpecified = true;
for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
@@ -580,79 +578,32 @@
"Power brackets should be specified for all scaling policies or none");
}
+ if (!allBracketsSpecified) {
+ mCpuPowerBracketCount = POWER_BRACKETS_UNSPECIFIED;
+ return;
+ }
+
mCpuPowerBracketCount = 0;
- if (allBracketsSpecified) {
- for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
- int policy = mCpuScalingPolicies.keyAt(i);
- CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
- final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
- if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
- throw new RuntimeException(
- "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
- + ", expected: "
- + cpuScalingPolicyPower.powerBrackets.length);
- }
-
- for (int j = 0; j < data.length; j++) {
- final int bracket = (int) Math.round(data[j]);
- cpuScalingPolicyPower.powerBrackets[j] = bracket;
- if (bracket > mCpuPowerBracketCount) {
- mCpuPowerBracketCount = bracket;
- }
- }
- }
- mCpuPowerBracketCount++;
- } else {
- double minPower = Double.MAX_VALUE;
- double maxPower = Double.MIN_VALUE;
- int stateCount = 0;
- for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
- int policy = mCpuScalingPolicies.keyAt(i);
- CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
- final int steps = cpuScalingPolicyPower.stepPower.length;
- for (int step = 0; step < steps; step++) {
- final double power = getAveragePowerForCpuScalingStep(policy, step);
- if (power < minPower) {
- minPower = power;
- }
- if (power > maxPower) {
- maxPower = power;
- }
- }
- stateCount += steps;
+ for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+ int policy = mCpuScalingPolicies.keyAt(i);
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+ final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
+ if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
+ throw new RuntimeException(
+ "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
+ + ", expected: "
+ + cpuScalingPolicyPower.powerBrackets.length);
}
- if (stateCount <= defaultCpuPowerBracketNumber) {
- mCpuPowerBracketCount = stateCount;
- int bracket = 0;
- for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
- CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
- final int steps = cpuScalingPolicyPower.stepPower.length;
- for (int step = 0; step < steps; step++) {
- cpuScalingPolicyPower.powerBrackets[step] = bracket++;
- }
- }
- } else {
- mCpuPowerBracketCount = defaultCpuPowerBracketNumber;
- final double minLogPower = Math.log(minPower);
- final double logBracket = (Math.log(maxPower) - minLogPower)
- / defaultCpuPowerBracketNumber;
-
- for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
- int policy = mCpuScalingPolicies.keyAt(i);
- CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
- final int steps = cpuScalingPolicyPower.stepPower.length;
- for (int step = 0; step < steps; step++) {
- final double power = getAveragePowerForCpuScalingStep(policy, step);
- int bracket = (int) ((Math.log(power) - minLogPower) / logBracket);
- if (bracket >= defaultCpuPowerBracketNumber) {
- bracket = defaultCpuPowerBracketNumber - 1;
- }
- cpuScalingPolicyPower.powerBrackets[step] = bracket;
- }
+ for (int j = 0; j < data.length; j++) {
+ final int bracket = (int) Math.round(data[j]);
+ cpuScalingPolicyPower.powerBrackets[j] = bracket;
+ if (bracket > mCpuPowerBracketCount) {
+ mCpuPowerBracketCount = bracket;
}
}
}
+ mCpuPowerBracketCount++;
}
private static class CpuScalingPolicyPower {
@@ -771,44 +722,13 @@
/**
* Returns the number of CPU power brackets: groups of states with similar power requirements.
+ * If power brackets are not specified, returns {@link #POWER_BRACKETS_UNSPECIFIED}
*/
public int getCpuPowerBracketCount() {
return mCpuPowerBracketCount;
}
/**
- * Description of a CPU power bracket: which cluster/frequency combinations are included.
- */
- public String getCpuPowerBracketDescription(CpuScalingPolicies cpuScalingPolicies,
- int powerBracket) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
- int policy = mCpuScalingPolicies.keyAt(i);
- CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
- int[] brackets = cpuScalingPolicyPower.powerBrackets;
- int[] freqs = cpuScalingPolicies.getFrequencies(policy);
- for (int step = 0; step < brackets.length; step++) {
- if (brackets[step] == powerBracket) {
- if (sb.length() != 0) {
- sb.append(", ");
- }
- if (mCpuScalingPolicies.size() > 1) {
- sb.append(policy).append('/');
- }
- if (step < freqs.length) {
- sb.append(freqs[step] / 1000);
- }
- sb.append('(');
- sb.append(String.format(Locale.US, "%.1f",
- getAveragePowerForCpuScalingStep(policy, step)));
- sb.append(')');
- }
- }
- }
- return sb.toString();
- }
-
- /**
* Returns the CPU power bracket corresponding to the specified scaling policy and frequency
* step
*/
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 1130a45..1a7efac 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -55,12 +55,12 @@
private static final int STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
public static final int MAX_STATS_ARRAY_LENGTH =
- 2 ^ Integer.bitCount(STATS_ARRAY_LENGTH_MASK) - 1;
+ (1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
public static final int MAX_UID_STATS_ARRAY_LENGTH =
- (2 ^ Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
+ (1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
/**
* Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 965277c..1c5f4f0 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -868,6 +868,11 @@
args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
+ // While `specializeAppProcess` sets the thread name on the process's main thread, this
+ // is distinct from the app process name which appears in stack traces, as the latter is
+ // sourced from the argument buffer of the Process class. Set the app process name here.
+ Zygote.setAppProcessName(args, TAG);
+
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 993e4e7..5fe086d 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -296,7 +296,6 @@
} else {
// child; result is a Runnable.
zygoteServer.setForkChild();
- Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary?
return result;
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a3e2706..a3e0016 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1105,10 +1105,9 @@
@UnsupportedAppUsage
public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
- if (isSpecialUserId(userId)) {
- // For secure password storage (that is required for special users such as FRP), the
- // underlying storage also enforces the deadline. Since we cannot store settings
- // for special users, don't.
+ if (userId == USER_FRP) {
+ // For secure password storage (that is required for FRP), the underlying storage also
+ // enforces the deadline. Since we cannot store settings for the FRP user, don't.
return deadline;
}
mLockoutDeadlines.put(userId, deadline);
@@ -1934,15 +1933,21 @@
}
/**
- * 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>
- * Whether the storage has been unlocked can be determined by
- * {@link StorageManager#isUserKeyUnlocked()}.
- *
+ * 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>
* 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/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 34ef7b3..5b95ee7 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -85,6 +85,18 @@
return false;
}
+static jlong android_hardware_OverlayProperties_createDefault(JNIEnv* env, jobject thiz) {
+ gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+ gui::OverlayProperties::SupportedBufferCombinations combination;
+ combination.pixelFormats = {HAL_PIXEL_FORMAT_RGBA_8888};
+ combination.standards = {HAL_DATASPACE_BT709};
+ combination.transfers = {HAL_DATASPACE_TRANSFER_SRGB};
+ combination.ranges = {HAL_DATASPACE_RANGE_FULL};
+ overlayProperties->combinations.emplace_back(combination);
+ overlayProperties->supportMixedColorSpaces = true;
+ return reinterpret_cast<jlong>(overlayProperties);
+}
+
// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------
@@ -150,6 +162,7 @@
(void*) android_hardware_OverlayProperties_write },
{ "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
(void*) android_hardware_OverlayProperties_read },
+ {"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
};
// clang-format on
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index f97d41b..262f5e8 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -42,13 +42,6 @@
return NULL;
}
- // b/274058082: Pass a copy of the key character map to avoid concurrent
- // access
- std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
- if (map != nullptr) {
- map = std::make_shared<KeyCharacterMap>(*map);
- }
-
ScopedLocalRef<jstring> descriptorObj(env,
env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str()));
if (!descriptorObj.get()) {
@@ -67,9 +60,14 @@
? layoutInfo->layoutType.c_str()
: NULL));
+ std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
+ std::unique_ptr<KeyCharacterMap> mapCopy;
+ if (map != nullptr) {
+ mapCopy = std::make_unique<KeyCharacterMap>(*map);
+ }
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
- map));
+ std::move(mapCopy)));
if (!kcmObj.get()) {
return NULL;
}
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 7f69e22..a79e37a 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -48,7 +48,7 @@
class NativeKeyCharacterMap {
public:
- NativeKeyCharacterMap(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map)
+ NativeKeyCharacterMap(int32_t deviceId, std::unique_ptr<KeyCharacterMap> map)
: mDeviceId(deviceId), mMap(std::move(map)) {}
~NativeKeyCharacterMap() {
@@ -58,16 +58,18 @@
return mDeviceId;
}
- inline const std::shared_ptr<KeyCharacterMap> getMap() const { return mMap; }
+ inline const std::unique_ptr<KeyCharacterMap>& getMap() const {
+ return mMap;
+ }
private:
int32_t mDeviceId;
- std::shared_ptr<KeyCharacterMap> mMap;
+ std::unique_ptr<KeyCharacterMap> mMap;
};
jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm) {
- NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, kcm);
+ std::unique_ptr<KeyCharacterMap> kcm) {
+ NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, std::move(kcm));
if (!nativeMap) {
return nullptr;
}
@@ -91,7 +93,7 @@
return 0;
}
- std::shared_ptr<KeyCharacterMap> kcm = nullptr;
+ std::unique_ptr<KeyCharacterMap> kcm;
// Check if map is a null character map
if (parcel->readBool()) {
kcm = KeyCharacterMap::readFromParcel(parcel);
@@ -99,7 +101,7 @@
return 0;
}
}
- NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, kcm);
+ NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, std::move(kcm));
return reinterpret_cast<jlong>(map);
}
@@ -230,9 +232,9 @@
}
static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
- const std::shared_ptr<KeyCharacterMap>& map1 =
+ const std::unique_ptr<KeyCharacterMap>& map1 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
- const std::shared_ptr<KeyCharacterMap>& map2 =
+ const std::unique_ptr<KeyCharacterMap>& map2 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
if (map1 == nullptr || map2 == nullptr) {
return map1 == map2;
diff --git a/core/jni/android_view_KeyCharacterMap.h b/core/jni/android_view_KeyCharacterMap.h
index be03353..a8aabd1 100644
--- a/core/jni/android_view_KeyCharacterMap.h
+++ b/core/jni/android_view_KeyCharacterMap.h
@@ -25,7 +25,7 @@
/* Creates a KeyCharacterMap object from the given information. */
extern jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm);
+ std::unique_ptr<KeyCharacterMap> kcm);
} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 178c0d0..9833598 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,7 @@
jfieldID height;
jfieldID xDpi;
jfieldID yDpi;
- jfieldID refreshRate;
+ jfieldID peakRefreshRate;
jfieldID vsyncRate;
jfieldID appVsyncOffsetNanos;
jfieldID presentationDeadlineNanos;
@@ -1232,7 +1232,7 @@
env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi);
env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
- env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+ env->SetFloatField(object, gDisplayModeClassInfo.peakRefreshRate, config.peakRefreshRate);
env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate);
env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
@@ -2396,7 +2396,7 @@
gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I");
gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F");
- gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F");
+ gDisplayModeClassInfo.peakRefreshRate = GetFieldIDOrDie(env, modeClazz, "peakRefreshRate", "F");
gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F");
gDisplayModeClassInfo.appVsyncOffsetNanos =
GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J");
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index 03e9a6a..1b24efa 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -39,7 +39,7 @@
explicit VelocityTrackerState(const VelocityTracker::Strategy strategy);
void clear();
- void addMovement(const MotionEvent* event);
+ void addMovement(const MotionEvent& event);
// TODO(b/32830165): consider supporting an overload that supports computing velocity only for
// a subset of the supported axes.
void computeCurrentVelocity(int32_t units, float maxVelocity);
@@ -57,7 +57,7 @@
mVelocityTracker.clear();
}
-void VelocityTrackerState::addMovement(const MotionEvent* event) {
+void VelocityTrackerState::addMovement(const MotionEvent& event) {
mVelocityTracker.addMovement(event);
}
@@ -102,13 +102,13 @@
static void android_view_VelocityTracker_nativeAddMovement(JNIEnv* env, jclass clazz, jlong ptr,
jobject eventObj) {
const MotionEvent* event = android_view_MotionEvent_getNativePtr(env, eventObj);
- if (!event) {
+ if (event == nullptr) {
LOG(WARNING) << "nativeAddMovement failed because MotionEvent was finalized.";
return;
}
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
- state->addMovement(event);
+ state->addMovement(*event);
}
static void android_view_VelocityTracker_nativeComputeCurrentVelocity(JNIEnv* env, jclass clazz,
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
new file mode 100644
index 0000000..8c33041
--- /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 4732702..5b0a502 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -99,6 +99,7 @@
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 025a57d..c5889ba 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -20,6 +20,7 @@
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 @@
}
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 70cb50f..7a19155 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 6daa5b9..8c91be8 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 66dd392..b664fe4f 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 34bc88c..b437a4b 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 24d7c34..52933aa 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-af/strings.xml b/core/res/res/values-af/strings.xml
index cd951cb..5037239 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programme wat batterykrag gebruik"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toeganklikheidgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans batterykrag"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> programme gebruik tans batterykrag"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik vir besonderhede oor battery- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Laat ’n metgeselapp toe om voorgronddienste van agtergrond af te begin"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoon is beskikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoon is geblokkeer"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan nie na skerm weerspieël nie"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik ’n ander kabel en probeer weer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel steun dalk nie skerms nie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Jou USB-C-kabel koppel dalk nie behoorlik aan skerms nie"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans albei skerms om inhoud te wys"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 42ba598..9a8bb62 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ባትሪ በመፍጀት ላይ ያሉ መተግበሪያዎች"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ማጉላት"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"የተደራሽነት አጠቃቀም"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ማሳያ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ባትሪ እየተጠቀመ ነው"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> መተግበሪያዎች ባትሪ እየተጠቀሙ ነው"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"በባትሪ እና ውሂብ አጠቃቀም ላይ ዝርዝሮችን ለማግኘት መታ ያድርጉ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"አጃቢ መተግበሪያ ከዳራ የፊት አገልግሎቶችን እንዲጀምር ያስችላል።"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ማይክሮፎን ይገኛል"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ገፅ በርቷል"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ይዘትን ለማሳየት ሁለቱንም ማሳያዎች እየተጠቀመ ነው"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68d7ff4..f6d7c64 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -294,6 +294,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"التطبيقات التي تستهلك البطارية"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"التكبير"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"استخدام \"أدوات تسهيل الاستخدام\""</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"الشاشة"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"يستخدم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> البطارية"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"تستخدم <xliff:g id="NUMBER">%1$d</xliff:g> من التطبيقات البطارية"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"انقر للحصول على تفاصيل حول البطارية واستخدام البيانات"</string>
@@ -2337,6 +2338,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"يسمح هذا الإذن للتطبيق المصاحب ببدء الخدمات التي تعمل في المقدّمة من الخلفية."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"الميكروفون متاح."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ميزة Dual Screen مفعّلة"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"يستخدم \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كلتا الشاشتين لعرض المحتوى."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ba3e756..df4f28a 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"বেটাৰী খৰচ কৰা এপ্সমূহ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বিবৰ্ধন"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"সাধ্য সুবিধাৰ ব্যৱহাৰ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিছপ্লে’"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টা এপে বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"বেটাৰী আৰু ডেটাৰ ব্যৱহাৰৰ বিষয়ে সবিশেষ জানিবলৈ টিপক"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"এটা সহযোগী এপক নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰিবলৈ দিয়ে।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্ৰ’ফ’নটো উপলব্ধ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen অন আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ সমল দেখুৱাবলৈ দুয়োখন ডিছপ্লে’ ব্যৱহাৰ কৰি আছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 4124dfa..8bfd8b5 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareyadan istifadə edən tətbiqlər"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Böyütmə"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Əlçatımlılıq istifadəsi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> batareyadan istifadə edir"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> tətbiq batareyadan istifadə edir"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya və data istifadəsi haqqında ətraflı məlumat üçün klikləyin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Kompanyon tətbiqinə ön fon xidmətlərini arxa fondan başlatmaq icazəsi verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon əlçatandır"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"İkili ekran aktivdir"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> məzmunu göstərmək üçün hər iki displeydən istifadə edir"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 3fd8dcb..48b5c02 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korišćenje Pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i video snimcima na uređaju"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i videima na uređaju"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Fizičke aktivnosti"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da pokrene usluge u prvom planu iz pozadine."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 715aad3..12effa0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Праграмы, якія выкарыстоўваюць акумулятар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Павелічэнне"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Выкарыстанне спецыяльных магчымасцей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дысплэй"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> выкарыстоўвае акумулятар"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Наступная колькасць праграм выкарыстоўваюць акумулятар: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Дакраніцеся, каб даведацца пра выкарыстанне трафіка і акумулятара"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Спадарожная праграма зможа запускаць актыўныя сэрвісы з фонавага рэжыму."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрафон даступны"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Уключана функцыя Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" выкарыстоўвае абодва экраны для паказу змесціва"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9dd971c..d18e4bc 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, използващи батерията"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Използване на услугите за достъпност"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва батерията"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> приложения използват батерията"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Докоснете за информация относно използването на батерията и преноса на данни"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Разрешава на дадено придружаващо приложение да стартира услуги на преден план, докато се изпълнява на заден план."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонът е налице"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функцията Dual Screen е включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва и двата екрана, за да показва съдържание"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 6f879e4..859f37d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"কিছু অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বড় করে দেখা"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"অ্যাক্সেসিবিলিটি সংক্রান্ত ব্যবহার"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিসপ্লে"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপটি ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টি অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ব্যাটারি এবং ডেটার ব্যবহারের বিশদ বিবরণের জন্য ট্যাপ করুন"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"কম্প্যানিয়ন অ্যাপকে, ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করার অনুমতি দেয়।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্রোফোন উপলভ্য আছে"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen চালু করা আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"কন্টেন্ট দেখানোর জন্য <xliff:g id="APP_NAME">%1$s</xliff:g> দুটি ডিসপ্লে ব্যবহার করছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b0f1905..8e9fe9c 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korištenje pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> troši bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje troše bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o potrošnji baterije i prijenosa podataka"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da iz pozadine pokrene usluge u prvom planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index be95847..dacdbf1 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacions que consumeixen bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliació"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ús de les funcions d\'accessibilitat"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> està consumint bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacions estan consumint bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca per obtenir informació sobre l\'ús de dades i de bateria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 32cff76..29e00fa 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikace spotřebovávají baterii"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zvětšení"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využití přístupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displej"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikace (<xliff:g id="NUMBER">%1$d</xliff:g>) využívají baterii"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o využití baterie a dat"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje doprovodné aplikaci spouštět z pozadí služby v popředí."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupný"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkce Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> používá k zobrazení obsahu oba displeje"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 75bf365..87703cd 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps, der bruger batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørrelse"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Brug af hjælpefunktioner"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skærm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps bruger batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryk for at se info om batteri- og dataforbrug"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillader, at en medfølgende app kan starte tjenester i forgrunden via tilladelser til tjenester i baggrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgængelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er aktiveret"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger begge skærme til at vise indhold"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index e087fe2..3faf328 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Strom verbrauchende Apps"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergrößerung"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Nutzung der Bedienungshilfen"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> verbraucht Strom"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> Apps verbrauchen Strom"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Für Details zur Akku- und Datennutzung tippen"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ermöglicht einer Companion-App, Dienste im Vordergrund aus dem Hintergrund zu starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon ist verfügbar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ist aktiviert"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> nutzt zum Anzeigen von Inhalten beide Displays"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 9b713c99..af53ddf 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Εφαρμογές που καταναλώνουν μπαταρία"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Μεγιστοποίηση"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Χρήση προσβασιμότητας"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Οθόνη"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί μπαταρία"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> εφαρμογές χρησιμοποιούν μπαταρία"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Πατήστε για λεπτομέρειες σχετικά με τη χρήση μπαταρίας και δεδομένων"</string>
@@ -1376,7 +1377,7 @@
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Εντοπίστηκε αναλογικό αξεσουάρ ήχου"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Η συνδεδεμένη συσκευή δεν είναι συμβατή με αυτό το τηλέφωνο. Πατήστε για να μάθετε περισσότερα."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"Συνδέθηκε ο εντοπ. σφαλμ. USB"</string>
- <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ./διόρθ. σφαλμ. USB"</string>
+ <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ. σφαλμ. USB"</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Συνδέθηκε ο ασύρματος εντοπισμός σφαλμάτων"</string>
<string name="adbwifi_active_notification_message" msgid="930987922852867972">"Πατήστε, για να απενεργοποιήσετε τον ασύρματο εντοπισμό σφαλμάτων"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Επιτρέπει σε μια συνοδευτική εφαρμογή να εκκινεί υπηρεσίες στο προσκήνιο από το παρασκήνιο."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Το μικρόφωνο είναι διαθέσιμο"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Η λειτουργία διπλής οθόνης είναι ενεργή"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Η εφαρμ. <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί και τις 2 οθόνες για εμφάνιση περιεχ."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index bc231f5..8917a82 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 29f4c78..5a0ed85 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5576054..bcf0790 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index ea95a513..7ebffc6 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index c09e6ce..b739768 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 723f833..ca9ff13 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está consumiendo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps están consumiendo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Presiona para obtener información sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 79c6e26..97b1888 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicaciones que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando la batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para ver información detallada sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 67c20f7..4a8666e 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Rakendused kasutavad akutoidet"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurendus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Juurdepääsetavuse kasutus"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekraan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab akutoidet"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> rakendust kasutab akutoidet"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Aku ja andmekasutuse üksikasjade nägemiseks puudutage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lubab kaasrakendusel taustal käivitada esiplaanil olevaid teenuseid."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon on saadaval"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screeni režiim on sisse lülitatud"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab sisu kuvamiseks mõlemat ekraani"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2150324..a163ed8 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Bateria kontsumitzen ari diren aplikazioak"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Lupa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erabilerraztasun-hobespenak"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantaila"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ari da bateria erabiltzen"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikazio ari dira bateria erabiltzen"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Sakatu bateria eta datu-erabilerari buruzko xehetasunak ikusteko"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Aurreko planoko zerbitzuak atzeko planotik abiarazteko baimena ematen die aplikazio osagarriei."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Erabilgarri dago mikrofonoa"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen aktibatuta dago"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bi pantailak erabiltzen ari da edukia erakusteko"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index de1ba1a..c5a2ee6 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"برنامههای مصرفکننده باتری"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"درشتنمایی"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"کاربرد دسترسپذیری"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"نمایشگر"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده کردن از باتری است"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> برنامه درحال استفاده کردن از باتری هستند"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"برای جزئیات مربوط به مصرف باتری و داده، ضربه بزنید"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"به برنامه همراه اجازه میدهد سرویسهای پیشنما را از پسزمینه راهاندازی کند."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"میکروفون دردسترس است"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"کابل USB-C شما ممکن است بهدرستی به نمایشگرها وصل نشود"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen روشن است"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> از هر دو نمایشگر برای نمایش محتوا استفاده میکند"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 22d48e2..4a08b90 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkua kuluttavat sovellukset"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurennus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Esteetön käyttö"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Näyttö"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää akkua."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> sovellusta käyttää akkua."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Katso lisätietoja akun ja datan käytöstä napauttamalla."</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Sallii kumppanisovelluksen aloittaa etualan palveluja taustalla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni on käytettävissä"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kaksoisnäyttö on päällä"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää molempia näyttöjä sisällön näyttämiseen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 928bf77..1b637db 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications qui sollicitent la pile"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Usage des fonctionnalités d\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sollicite la pile"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications sollicitent la pile"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet à une application compagnon en arrière-plan de lancer des services d\'avant-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le microphone est accessible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher le contenu"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d7d29a4..4f8de0c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications utilisant la batterie"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilisation de l\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise la batterie"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications utilisent la batterie"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Appuyer pour obtenir des informations sur l\'utilisation de la batterie et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Autorise une application associée à lancer des services de premier plan à partir de l\'arrière-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le micro est disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Double écran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Double écran activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher du contenu"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index e2073fb..b25cfcb 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacións que consomen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacións están consumindo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para obter información sobre o uso de datos e a batería"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que unha aplicación complementaria, desde un segundo plano, inicie servizos en primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O micrófono está dispoñible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas as pantallas para mostrar contido"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 82b4a0d..fae5ebc 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ઍપ બૅટરીનો વપરાશ કરી રહ્યાં છે"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"મોટું કરવાની સુવિધા"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ઍક્સેસિબિલિટી વપરાશ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ડિસ્પ્લે"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> બૅટરીનો ઉપયોગ કરી રહ્યું છે"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ઍપ બૅટરીનો ઉપયોગ કરી રહ્યાં છે"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"બૅટરી અને ડેટા વપરાશ વિશેની વિગતો માટે ટૅપ કરો"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"સાથી ઍપને બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરવાની મંજૂરી આપે છે."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"માઇક્રોફોન ઉપલબ્ધ છે"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ચાલુ છે"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"કન્ટેન્ટ બતાવવા માટે <xliff:g id="APP_NAME">%1$s</xliff:g> બન્ને ડિસ્પ્લેનો ઉપયોગ કરી રહી છે"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 1f3a377..d4eb380 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बैटरी की खपत करने वाले ऐप"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ज़ूम करने की सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सुलभता सुविधाओं का इस्तेमाल"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिसप्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बैटरी का इस्तेमाल कर रहा है"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ऐप बैटरी का इस्तेमाल कर रहे हैं"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बैटरी और डेटा खर्च की जानकारी के लिए छूएं"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"इससे साथी ऐप्लिकेशन को बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति मिलती है."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफ़ोन इस्तेमाल किया जा सकता है"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen की सुविधा चालू है"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, कॉन्टेंट दिखाने के लिए दोनों स्क्रीन का इस्तेमाल कर रहा है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 389a956..81ac641 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Upotreba pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje koriste bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite da biste vidjeli pojedinosti o potrošnji baterije i podatkovnom prometu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Popratnoj aplikaciji omogućuje da iz pozadine pokrene usluge u prednjem planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Uključen je dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> upotrebljava oba zaslona za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4bce83b..9a389db 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkumulátort használó alkalmazások"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Nagyítás"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Kisegítő lehetőségek használata"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Kijelző"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Koppintson az akkumulátor- és adathasználat részleteinek megtekintéséhez"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lehetővé teszi a társalkalmazások számára, hogy előtérben futó szolgáltatásokat indítsanak a háttérből."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"A mikrofon rendelkezésre áll"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A Dual Screen funkció be van kapcsolva"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> mindkét kijelzőt használja a tartalmak megjelenítésére"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index e74ec54..ccea0cc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Մարտկոցի լիցքը ծախսող հավելվածներ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Խոշորացում"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Հատուկ գործառույթների օգտագործում"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Էկրան"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"«<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածը ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> հավելված ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Հպեք՝ մարտկոցի և թրաֆիկի մանրամասները տեսնելու համար"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Թույլատրում է ուղեկցող հավելվածին ակտիվ ծառայություններ գործարկել ֆոնային ռեժիմից։"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Խոսափողը հասանելի է"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen-ը միացված է"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն օգտագործում է երկու էկրանները"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dc0993e..47bbefd 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikasi yang menggunakan baterai"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan aksesibilitas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Layar"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan baterai"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikasi sedang meggunakan baterai"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketuk untuk melihat detail penggunaan baterai dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Mengizinkan aplikasi pendamping memulai layanan latar depan dari latar belakang."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen aktif"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua layar untuk menampilkan konten"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 2af8969..f666308 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Forrit sem nota rafhlöðuorku"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Stækkun"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Aðgengisnotkun"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjár"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> notar rafhlöðuorku"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> forrit nota rafhlöðuorku"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ýttu til að fá upplýsingar um rafhlöðu- og gagnanotkun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leyfir fylgiforriti að ræsa forgrunnsþjónustur úr bakgrunni."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Hljóðnemi er í boði"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kveikt er á tveimur skjám"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> er að nota báða skjái til að sýna efni"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 7ea502c..e6bc552 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"App che consumano la batteria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ingrandimento"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilizzo dell\'accessibilità"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> sta consumando la batteria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> app stanno consumando la batteria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocca per conoscere i dettagli sull\'utilizzo dei dati e della batteria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Consente a un\'app complementare di avviare servizi in primo piano dal background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfono disponibile"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Doppio schermo attivo"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> sta usando entrambi i display per mostrare contenuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 22cbab2..53ec382 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"אפליקציות שמרוקנות את הסוללה"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"הגדלה"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"שימוש בנגישות"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"מסך"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> אפליקציות משתמשות בסוללה"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"אפשר להקיש כדי לקבל פרטים על צריכה של נתונים וסוללה"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ההרשאה הזו מאפשרת לאפליקציה נלווית להפעיל מהרקע שירותים שפועלים בחזית."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"המיקרופון זמין"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"מצב שני מסכים מופעל"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בשני המסכים כדי להציג תוכן"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 06b3445..961a8da87 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"電池を消費しているアプリ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"拡大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ユーザー補助の使用"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ディスプレイ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」がバッテリーを使用しています"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個のアプリが電池を使用しています"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"タップしてバッテリーやデータの使用量を確認"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"バックグラウンドからのフォアグラウンド サービスの起動をコンパニオン アプリに許可します。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"マイクを利用できます"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"デュアル スクリーン: ON"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>は 2 画面でコンテンツを表示しています"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c8f6621f4..27f596b 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ბატარეის მხარჯავი აპები"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"გადიდება"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"მარტივი წვდომის გამოყენება"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ეკრანი"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ბატარეას"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"ბატარეას <xliff:g id="NUMBER">%1$d</xliff:g> აპი იყენებს"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"შეეხეთ ბატარეისა და მონაცემების მოხმარების შესახებ დეტალური ინფორმაციისთვის"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"საშუალებას აძლევს კომპანიონ აპს, რომ გაუშვას უპირატესი სერვისები ფონური რეჟიმიდან."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"მიკროფონი ხელმისაწვდომია"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ორმაგი ეკრანი ჩართულია"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ორივე ეკრანს შინაარსის საჩვენებლად"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1272c24..1faea61 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Батареяны пайдаланып жатқан қолданбалар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ұлғайту"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Арнайы мүмкіндіктерді қолдану"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батареяны пайдалануда"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> қолданба батареяны пайдалануда"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Қосымша қолданбаға экрандық режимдегі қызметтерді фоннан іске қосуға рұқсат беріледі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон қолжетімді."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen функциясы қосулы"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы контентті көрсету үшін екі дисплейді де пайдаланады."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 807761e..470c39e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"កម្មវិធីដែលកំពុងប្រើថ្ម"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ការពង្រីក"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ការប្រើប្រាស់ភាពងាយស្រួល"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ផ្ទាំងអេក្រង់"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"កម្មវិធីចំនួន <xliff:g id="NUMBER">%1$d</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ចុចដើម្បីមើលព័ត៌មានលម្អិតអំពីការប្រើប្រាស់ទិន្នន័យ និងថ្ម"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ។"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"អាចប្រើមីក្រូហ្វូនបាន"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវបានទប់ស្កាត់"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នកប្រហែលជាមិនអាចភ្ជាប់ផ្ទាំងអេក្រង់បានត្រឹមត្រូវទេ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"អេក្រង់ពីរត្រូវបានបើក"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើផ្ទាំងអេក្រង់ទាំងពីរដើម្បីបង្ហាញខ្លឹមសារ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 742dfbb..e2f24f6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ಅಪ್ಲಿಕೇಶನ್ಗಳು ಬ್ಯಾಟರಿಯನ್ನು ಉಪಯೋಗಿಸುತ್ತಿವೆ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ಹಿಗ್ಗಿಸುವಿಕೆ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ಪ್ರವೇಶಿಸುವಿಕೆಯ ಬಳಕೆ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ಡಿಸ್ಪ್ಲೇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್, ಬ್ಯಾಟರಿಯನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ಅಪ್ಲಿಕೇಶನ್ಗಳು ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿವೆ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ಬ್ಯಾಟರಿ,ಡೇಟಾ ಬಳಕೆಯ ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ಆನ್ ಆಗಿದೆ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ಕಂಟೆಂಟ್ ಅನ್ನು ತೋರಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಎರಡೂ ಡಿಸ್ಪ್ಲೇಗಳನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index d2b6dc8..0608283 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"배터리를 소모하는 앱"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"확대"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"접근성 사용"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"디스플레이"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 배터리 사용 중"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"앱 <xliff:g id="NUMBER">%1$d</xliff:g>개에서 배터리 사용 중"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"탭하여 배터리 및 데이터 사용량 확인"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"호환 앱이 백그라운드에서 포그라운드 서비스를 시작할 수 있게 허용합니다."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"마이크 사용 가능"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen 켜짐"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 두 화면을 모두 사용하여 콘텐츠를 표시합니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 56c0696..853221e 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Колдонмолор батареяңызды коротууда"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Чоңойтуу"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Атайын мүмкүнчүлүктөрдүн колдонулушу"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу батареяны пайдаланып жатат"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> колдонмо батареяны пайдаланып жатат"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батареянын кубаты жана трафиктин көлөмү жөнүндө билүү үчүн таптап коюңуз"</string>
@@ -1384,7 +1385,7 @@
<string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Сыноо программасынын режими иштетилди"</string>
<string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Сыноо программасынын режимин өчүрүү үчүн баштапкы параметрлерге кайтарыңыз."</string>
<string name="console_running_notification_title" msgid="6087888939261635904">"Сериялык консоль иштетилди"</string>
- <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Аны өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
+ <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
<string name="mte_override_notification_title" msgid="4731115381962792944">"Cынамык MTE иштетилди"</string>
<string name="mte_override_notification_message" msgid="2441170442725738942">"Иштин майнаптуулугуна жана туруктуулугуна кедергиси тийиши мүмкүн. Өчүрүү үчүн түзмөктү өчүрүп-күйгүзүңүз. Эгер arm64.memtag.bootctl аркылуу иштетилген болсо, алдын ала \"none\" маанисин орнотуңуз."</string>
<string name="usb_contaminant_detected_title" msgid="4359048603069159678">"USB портунда суюктук же урандылар бар"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Көмөкчү колдонмого активдүү кызматтарды фондо иштетүүгө уруксат берет."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон жеткиликтүү"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen күйүк"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контентти эки түзмөктө тең көрсөтүүдө"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 8892f60..c743fc0 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ແອັບທີ່ກຳລັງໃຊ້ແບັດເຕີຣີ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ການຂະຫຍາຍ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ການໃຊ້ການຊ່ວຍເຂົ້າເຖິງ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ຈໍສະແດງຜົນ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ແອັບກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ແຕະເພື່ອເບິ່ງລາຍລະອຽດການນຳໃຊ້ແບັດເຕີຣີ ແລະ ອິນເຕີເນັດ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ອະນຸຍາດຈາກເບື້ອງຫຼັງໃຫ້ແອັບຊ່ວຍເຫຼືອເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ໄມໂຄຣໂຟນພ້ອມໃຫ້ນຳໃຊ້"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ເປີດ Dual Screen ຢູ່"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ຈໍສະແດງຜົນທັງສອງເພື່ອສະແດງເນື້ອຫາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 8b4ff97..ff665c5 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programos, naudojančios akumuliatoriaus energiją"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Didinimas"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pritaikomumo funkcijų naudojimas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekranas"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja akumuliatoriaus energiją"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programų, naudojančių akumuliatoriaus energiją: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Palieskite ir sužinokite išsamios informacijos apie akumuliatoriaus bei duomenų naudojimą"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leidžiama papildomai programai paleisti priekinio plano paslaugas fone."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonas pasiekiamas"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Įjungta „Dual Screen“"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja abu ekranus turiniui rodyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c509378..4d369aa 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Lietotnes, kas patērē akumulatora jaudu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Palielinājums"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pieejamības lietojums"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displejs"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> izmanto akumulatoru"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> lietotne(-es) izmanto akumulatoru"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pieskarieties, lai skatītu detalizētu informāciju par akumulatora un datu lietojumu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ļauj palīglietotnei sākt priekšplāna pakalpojumus no fona."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofons ir pieejams."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ieslēgts Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> satura rādīšanai izmanto abus displejus."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 109e967..29c9de8 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликации што ја трошат батеријата"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Зголемување"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Користење на пристапноста"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерија"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апликации користат батерија"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Допрете за детали за батеријата и потрошениот интернет"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволува придружна апликација да започне услуги во преден план од заднината."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонот е достапен"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Вклучен е Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ги користи двата екрани за да прикажува содржини"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 9ccf8b5..46587ee 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"മാഗ്നിഫിക്കേഷൻ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ഉപയോഗസഹായി ഉപയോഗം"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ഡിസ്പ്ലേ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ബാറ്ററി, ഡാറ്റ ഉപയോഗം എന്നിവയുടെ വിശദാംശങ്ങളറിയാൻ ടാപ്പുചെയ്യുക"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്തു"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ഡ്യുവൽ സ്ക്രീൻ ഓണാണ്"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ഉള്ളടക്കം കാണിക്കാൻ <xliff:g id="APP_NAME">%1$s</xliff:g> രണ്ട് ഡിസ്പ്ലേകളും ഉപയോഗിക്കുന്നു"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index a9ef358..62a162e 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апп батарей ашиглаж байна"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Томруулах"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Хандалтын ашиглалт"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дэлгэц"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батарей ашиглаж байна"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апп батарей ашиглаж байна"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарей, дата ашиглалтын талаар дэлгэрэнгүйг харахын тулд товшино уу"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дэмжигч аппад нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэхийг зөвшөөрнө."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофоныг ашиглах боломжгүй байна"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen асаалттай байна"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контент харуулахын тулд хоёр дэлгэцийг хоёуланг нь ашиглаж байна"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 485e9b6..87227ee 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बॅटरी लवकर संपवणारी अॅप्स"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"मॅग्निफिकेशन"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"अॅक्सेसिबिलिटी वापर"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बॅटरी वापरत आहे"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> अॅप्स बॅटरी वापरत आहेत"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्या तपशीलांसाठी टॅप करा"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"सहयोगी अॅपला बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करण्याची अनुमती देते."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"मायक्रोफोन उपलब्ध आहे"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"मायक्रोफोन ब्लॉक केलेला आहे"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेवर मिरर करू शकत नाही"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"वेगळी केबल वापरून पुन्हा प्रयत्न करा"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"केबल कदाचित डिस्प्लेना सपोर्ट करणार नाही"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तुमची USB-C केबल कदाचित डिस्प्लेना योग्यरीत्या कनेक्ट होणार नाही"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen सुरू आहे"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"आशय दाखवण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> दोन्ही डिस्प्ले वापरत आहे"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3d3fc7c..7794150 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apl yang menggunakan bateri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan kebolehaksesan"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Paparan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan bateri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apl sedang menggunakan bateri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketik untuk mendapatkan butiran tentang penggunaan kuasa bateri dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Benarkan apl rakan memulakan perkhidmatan latar depan dari latar."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dwiskrin dihidupkan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua-dua paparan untuk menunjukkan kandungan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 39dd043..3aaaf8b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"အက်ပ်များက ဘက်ထရီကုန်စေသည်"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ချဲ့ခြင်း"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"အများသုံးစွဲနိုင်မှုကို အသုံးပြုမှု"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ဖန်သားပြင်"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> က ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"အက်ပ် <xliff:g id="NUMBER">%1$d</xliff:g> ခုက ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ဘက်ထရီနှင့် ဒေတာအသုံးပြုမှု အသေးစိတ်ကို ကြည့်ရန် တို့ပါ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ဖွင့်ထားသည်"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အကြောင်းအရာကို ပြရန် ဖန်သားပြင်နှစ်ခုစလုံးကို သုံးနေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 5b1f77c..9d6e805 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apper bruker batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Bruk av Tilgjengelighet"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apper bruker batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trykk for detaljer om batteri- og databruk"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lar en følgeapp starte forgrunnstjenester fra bakgrunnen."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgjengelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker begge skjermene til å vise innhold"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 603fad34..3b7de10 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"एपहरूले ब्याट्री खपत गर्दै छन्"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"जुम इन गर्ने सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सर्वसुलभतासम्बन्धी सेवाहरूको प्रयोग"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले ब्याट्री प्रयोग गर्दै छ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> एपहरूले ब्याट्री प्रयोग गर्दै छन्"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ब्याट्री र डेटाका प्रयोग सम्बन्धी विवरणहरूका लागि ट्याप गर्नुहोस्"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"यसले सहयोगी एपलाई ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति दिन्छ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफोन अनम्युट गरिएको छ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen अन छ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले सामग्री देखाउन दुई वटै डिस्प्ले प्रयोग गरिरहेको छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 50e261f..ae7d366 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps die de batterij gebruiken"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toegankelijkheidsgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Scherm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt de batterij"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps gebruiken de batterij"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik voor batterij- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hiermee kan een bijbehorende app services op de voorgrond vanuit de achtergrond starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfoon is beschikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen staat aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt beide schermen om content te tonen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 52c9bf2..af76df8 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ଆପ୍ଗୁଡ଼ିକ ବ୍ୟାଟେରୀ ଖର୍ଚ୍ଚ କରିଥା\'ନ୍ତି"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ମେଗ୍ନିଫିକେସନ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ଆକ୍ସେସିବିଲିଟୀ ବ୍ୟବହାର"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ଡିସପ୍ଲେ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛି"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>ଟି ଆପ୍ ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛନ୍ତି"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ବ୍ୟାଟେରୀ ଏବଂ ଡାଟା ବ୍ୟବହାର ଉପରେ ବିବରଣୀ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରିବାକୁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ ଅଛି"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ଚାଲୁ ଅଛି"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ବିଷୟବସ୍ତୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଉଭୟ ଡିସପ୍ଲେକୁ ବ୍ୟବହାର କରୁଛି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8034be8..243e3e5 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ਬੈਟਰੀ ਦੀ ਖਪਤ ਕਰਨ ਵਾਲੀਆਂ ਐਪਾਂ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ਵੱਡਦਰਸ਼ੀਕਰਨ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ਪਹੁੰਚਯੋਗਤਾ ਵਰਤੋਂ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ਡਿਸਪਲੇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ਐਪਾਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ਬੈਟਰੀ ਅਤੇ ਡਾਟਾ ਵਰਤੋਂ ਸਬੰਧੀ ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ਚਾਲੂ ਹੈ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਸਮੱਗਰੀ ਨੂੰ ਦਿਖਾਉਣ ਲਈ ਦੋਵੇਂ ਡਿਸਪਲੇਆਂ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀ ਹੈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b66ec02..18bea3f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacje zużywające baterię"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Powiększenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Użycie ułatwień dostępu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Wyświetlacz"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> zużywa baterię"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Liczba aplikacji zużywających baterię: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Zezwala aplikacji towarzyszącej na uruchamianie usług działających na pierwszym planie, podczas gdy sama działa w tle."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon jest dostępny"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Włączono podwójny ekran"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> korzysta z obu wyświetlaczy, aby pokazać treści"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 628627d..486318d 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c83491e..784f2a9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão a consumir bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilização da acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecrã"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que uma app associada em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcionalidade Dual Screen ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a usar ambos os ecrãs para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 628627d..486318d 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 3389c63..88f5044 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicațiile consumă bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Mărire"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Folosirea accesibilității"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicații folosesc bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite unei aplicații partenere să inițieze servicii în prim-plan din fundal."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfonul este disponibil"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcția Dual screen este activată"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește ambele ecrane pentru a afișa conținut"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 920168a..7f5e87f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, расходующие заряд"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Сервисы специальных возможностей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" расходует заряд"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Несколько приложений (<xliff:g id="NUMBER">%1$d</xliff:g>) расходуют заряд"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Нажмите, чтобы проверить энергопотребление и трафик"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Сопутствующее приложение сможет запускать активные службы из фонового режима."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон доступен."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функция Dual Screen включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> использует оба экрана."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5f743a4..e90f1a2 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"බැටරිය භාවිත කරන යෙදුම්"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"විශාලනය"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ප්රවේශ්යතා භාවිතය"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"සංදර්ශකය"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"යෙදුම් <xliff:g id="NUMBER">%1$d</xliff:g>ක් බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"බැටරි හා දත්ත භාවිතය පිළිබඳව විස්තර සඳහා තට්ටු කරන්න"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කිරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"මයික්රෆෝනය තිබේ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්රෆෝනය අවහිර කර ඇත"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen සක්රීයයි"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"අන්තර්ගතය පෙන්වීමට <xliff:g id="APP_NAME">%1$s</xliff:g> සංදර්ශන දෙකම භාවිත කරයි"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6bbc110..3da576c 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikácie spotrebúvajúce batériu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zväčšenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využitie dostupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Obrazovka"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> používa batériu"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikácie (<xliff:g id="NUMBER">%1$d</xliff:g>) používajú batériu"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o batérii a spotrebe dát"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje sprievodnej aplikácii spúšťať služby na popredí z pozadia."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofón je k dispozícii"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkcia Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> zobrazuje obsah na oboch obrazovkách"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3999f9f..5b731b7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije, ki porabljajo energijo baterije"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povečava"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uporaba funkcij za dostopnost"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> porablja energijo baterije"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Toliko aplikacij porablja energijo baterije: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dotaknite se za prikaz podrobnosti porabe baterije in prenosa podatkov"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Spremljevalni aplikaciji dovoljuje, da storitve v ospredju zažene iz ozadja."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je na voljo"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je vklopljen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> uporablja oba zaslona za prikaz vsebine."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index e746463..a4fd1bf1 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacionet që konsumojnë baterinë"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zmadhimi"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Përdorimi i qasshmërisë"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekrani"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikacione po përdorin baterinë"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trokit për detaje mbi baterinë dhe përdorimin e të dhënave"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lejon një aplikacion shoqërues të fillojë shërbimet në plan të parë nga sfondi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni ofrohet"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen është aktiv"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> po i përdor të dyja ekranet për të shfaqur përmbajtje"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index b5aefaa..e5ae2f7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликације које троше батерију"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увећање"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Коришћење Приступачности"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Додирните за детаље о батерији и потрошњи података"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видео снимцима на уређају"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видеима на уређају"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Физичке активности"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон је доступан"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen је укључен"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи оба екрана за приказивање садржаја"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a24ca19..f3917fe 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Appar som drar batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Förstoring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Tillgänglighetsanvändning"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skärm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> drar batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> appar drar batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryck för information om batteri- och dataanvändning"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillåter att en tillhörande app startar förgrundstjänster i bakgrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen är tillgänglig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen är på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> använder båda skärmarna för att visa innehåll"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7171486..95c1b25 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programu zinazotumia betri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukuzaji"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Matumizi ya zana za ufikivu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skrini"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Huruhusu programu oanifu kuanzisha huduma zinazoonekana kwenye skrini kutoka katika huduma zinazoendelea chinichini."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Maikrofoni inapatikana"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen imewasha"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia skrini zote kuonyesha maudhui"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4fe363e..9ad4bd2 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"பேட்டரியைப் பயன்படுத்தும் ஆப்ஸ்"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"பெரிதாக்கல்"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"அணுகல்தன்மை உபயோகம்"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"டிஸ்ப்ளே"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகிறது"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகின்றன"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"பேட்டரி மற்றும் டேட்டா உபயோக விவரங்களைக் காண, தட்டவும்"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"பின்னணியிலிருந்து முன்புலச் சேவைகளைத் தொடங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"மைக்ரோஃபோன் இயக்கப்பட்டது"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"இரட்டைத் திரை அம்சம் இயக்கத்தில் உள்ளது"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"உள்ளடக்கத்தைக் காட்டுவதற்கு இரண்டு டிஸ்ப்ளேக்களையும் <xliff:g id="APP_NAME">%1$s</xliff:g> பயன்படுத்துகிறது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index aa22aea..d1c336d 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"బ్యాటరీని ఉపయోగిస్తున్న యాప్లు"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"మ్యాగ్నిఫికేషన్"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"యాక్సెసిబిలిటీ వినియోగం"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"డిస్ప్లే చేయండి"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> బ్యాటరీని ఉపయోగిస్తోంది"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> యాప్లు బ్యాటరీని ఉపయోగిస్తున్నాయి"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"బ్యాటరీ మరియు డేటా వినియోగ వివరాల కోసం నొక్కండి"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"బ్యాక్గ్రౌండ్ నుండి ఫోర్గ్రౌండ్ సర్వీస్లను ప్రారంభించడానికి సహాయక యాప్ను అనుమతిస్తుంది."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ఆన్లో ఉంది"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"కంటెంట్ను చూపడం కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రెండు డిస్ప్లేలనూ ఉపయోగిస్తోంది"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index eb4e2f7..4f53fcd 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"แอปหลายแอปกำลังใช้แบตเตอรี่"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"การขยาย"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"การใช้งานการช่วยเหลือพิเศษ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"จอแสดงผล"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"แอป <xliff:g id="NUMBER">%1$d</xliff:g> แอปกำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"แตะเพื่อดูรายละเอียดเกี่ยวกับแบตเตอรี่และปริมาณการใช้อินเทอร์เน็ต"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"อนุญาตจากเบื้องหลังให้แอปที่ใช้ร่วมกันเริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้า"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ไมโครโฟนพร้อมใช้งาน"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen เปิดอยู่"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้จอแสดงผลทั้งสองจอเพื่อแสดงเนื้อหา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7249c51..2477698 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Mga app na kumokonsumo ng baterya"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pag-magnify"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Paggamit sa pagiging accessible"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Gumagamit ng baterya ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Gumagamit ng baterya ang <xliff:g id="NUMBER">%1$d</xliff:g> (na) app"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"I-tap para sa mga detalye tungkol sa paggamit ng baterya at data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Nagbibigay-daan sa kasamang app na magsimula ng mga serbisyo sa foreground mula sa background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Available ang mikropono"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Naka-on ang dual screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang parehong display para magpakita ng content"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8f02efd..2ebfe91 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Pil kullanan uygulamalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Büyütme"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erişilebilirlik kullanımı"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> pil kullanıyor"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> uygulama pil kullanıyor"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pil ve veri kullanımı ile ilgili ayrıntılar için dokunun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tamamlayıcı uygulamanın arka plandan ön plan hizmetlerini başlatmasına izin verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon kullanılabilir"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen açık"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, içeriği göstermek için her iki ekranı da kullanıyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d34e976..d4cd207 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Додатки, що використовують заряд акумулятора"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Збільшення"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Використання спеціальних можливостей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Додатків, що використовують заряд акумулятора: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Торкніться, щоб перевірити використання акумулятора й трафік"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволяє супутньому додатку запускати активні сервіси у фоновому режимі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрофон доступний"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen увімкнено"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує обидва екрани для показу контенту"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 631a573..8634294 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ایپس بیٹری خرچ کر رہی ہیں"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"میگنیفکیشن"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ایکسیسبیلٹی کا استعمال"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ڈسپلے"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> بیٹری کا استعمال کر رہی ہے"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ایپس بیٹری کا استعمال کر رہی ہیں"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"بیٹری اور ڈیٹا استعمال کے بارے میں تفصیلات کے لیے تھپتھپائیں"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ساتھی ایپ کو پس منظر سے پیش منظر کی سروسز شروع کرنے کی اجازت دیتی ہے۔"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"مائیکروفون دستیاب ہے"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"دوہری اسکرین آن ہے"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"مواد دکھانے کیلئے <xliff:g id="APP_NAME">%1$s</xliff:g> دونوں ڈسپلیز استعمال کر رہی ہے"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4262342..fdea194 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareya quvvatini sarflayotgan ilovalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Kattalashtirish"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Qulayliklar ishlatilishi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ta ilova batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya va trafik sarfi tafsilotlari uchun ustiga bosing"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hamroh ilovaga faol xizmatlarni fonda ishga tushirishga ruxsat beradi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon yoqildi"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ikki ekranli rejim yoniq"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kontentni ikkala ekranda chiqarmoqda"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 78318b1..12a61ed 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Các ứng dụng tiêu thụ pin"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Phóng to"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Việc sử dụng tính năng hỗ trợ tiếp cận"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Màn hình"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang sử dụng pin"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng đang sử dụng pin"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Nhấn để biết chi tiết về mức sử dụng dữ liệu và pin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Cho phép một ứng dụng đồng hành bắt đầu các dịch vụ trên nền trước từ nền."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Micrô đang hoạt động"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Chế độ Dual screen bật"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang dùng cả hai màn hình để thể hiện nội dung"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 16c1013..d79a772 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"消耗电量的应用"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大功能"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"无障碍功能使用情况"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"显示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在消耗电量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 个应用正在消耗电量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"点按即可详细了解电量和流量消耗情况"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允许配套应用从后台启动前台服务。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麦克风可用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"双屏幕功能已开启"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在使用双屏幕显示内容"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 06ec1dd..fe3644e 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情況"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"顯示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在使用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕按即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"可以使用麥克風"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"已開啟雙螢幕功能"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 81bdc37..5b65201 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情形"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"螢幕"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在耗用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在耗用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕觸即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麥克風已可使用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麥克風已封鎖"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他傳輸線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"傳輸線可能不支援螢幕"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C 傳輸線可能未妥善連接到螢幕"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"雙螢幕功能已啟用"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a2eb545..b701abe 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Izinhlelo zokusebenza ezidla ibhethri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukukhuliswa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ukusetshenziswa kokufinyeleleka"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Bonisa"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa ibhethri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> izinhlelo zokusebenza zisebenzisa ibhethri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Thepha ngemininingwane ekusetshenzisweni kwebhethri nedatha"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ivumela i-app ehambisanayo ukuthi iqale amasevisi angaphambili kusukela ngemuva."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Imakrofoni iyatholakala"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Isikrini esikabili sivuliwe"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa zombili izibonisi ukukhombisa okuqukethwe"</string>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index a6830a6..c0c6e05 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -135,11 +135,12 @@
<drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
<drawable name="notification_template_divider">#29000000</drawable>
<drawable name="notification_template_divider_media">#29ffffff</drawable>
- <color name="notification_primary_text_color_light">@color/primary_text_default_material_light</color>
- <color name="notification_primary_text_color_dark">@color/primary_text_default_material_dark</color>
- <color name="notification_secondary_text_color_light">@color/primary_text_default_material_light</color>
+ <color name="notification_primary_text_color_light">@color/system_on_surface_light</color>
+ <color name="notification_primary_text_color_dark">@color/system_on_surface_dark</color>
+ <!-- Note that the primary and secondary notification text colors are, in fact, the same. -->
+ <color name="notification_secondary_text_color_light">@color/system_on_surface_light</color>
+ <color name="notification_secondary_text_color_dark">@color/system_on_surface_dark</color>
<item name="notification_secondary_text_disabled_alpha" format="float" type="dimen">0.38</item>
- <color name="notification_secondary_text_color_dark">@color/primary_text_default_material_dark</color>
<color name="notification_default_color_dark">#ddffffff</color>
<color name="notification_default_color_light">#a3202124</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5a1f2d1a..ab71b41 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 8e1c09e..14bbb96 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 054d10c..6be553b 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -40,6 +40,8 @@
"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 d638fed..d4a88c4 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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 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 @@
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 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 @@
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 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b9f4c3f..03de143 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.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 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 @@
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 @@
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 @@
@Mock
private ICloseHandle mCloseHandleMock;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
public void getType_forBandDescriptor() {
RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -927,6 +962,27 @@
}
@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 @@
}
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 e348a51..3891acc 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 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 @@
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 @@
@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 @@
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 @@
}
@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 @@
@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 @@
}
@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 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 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 @@
}
@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 6a6a951..7ca806b 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.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.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 @@
@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 @@
}
@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 @@
}
@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/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 72969f7..37a499a 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -104,19 +104,15 @@
private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
private static final Path[] UI_TRACES_PREDUMPED = {
+ Paths.get("/data/misc/perfetto-traces/bugreport/systrace.pftrace"),
Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
- Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
- Paths.get("/data/misc/wmtrace/transition_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
};
- private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
- Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
- };
private Handler mHandler;
private Executor mExecutor;
@@ -210,7 +206,7 @@
mBrm.preDumpUiData();
waitTillDumpstateExitedOrTimeout();
- List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+ List<File> expectedPreDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
BugreportCallbackImpl callback = new BugreportCallbackImpl();
mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
@@ -225,7 +221,6 @@
assertFdsAreClosed(mBugreportFd);
assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
- assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
@@ -240,9 +235,9 @@
// In some corner cases, data dumped as part of the full bugreport could be the same as the
// pre-dumped data and this test would fail. Hence, here we create fake/artificial
// pre-dumped data that we know it won't match with the full bugreport data.
- createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system");
+ createFakeTraceFiles(UI_TRACES_PREDUMPED);
- List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+ List<File> preDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED);
BugreportCallbackImpl callback = new BugreportCallbackImpl();
mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
@@ -257,7 +252,6 @@
assertFdsAreClosed(mBugreportFd);
assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
- assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
@@ -480,7 +474,32 @@
return f;
}
- private static void startPreDumpedUiTraces() {
+ private static void startPreDumpedUiTraces() throws Exception {
+ // Perfetto traces
+ String perfettoConfig =
+ "buffers: {\n"
+ + " size_kb: 2048\n"
+ + " fill_policy: RING_BUFFER\n"
+ + "}\n"
+ + "data_sources: {\n"
+ + " config {\n"
+ + " name: \"android.surfaceflinger.transactions\"\n"
+ + " }\n"
+ + "}\n"
+ + "bugreport_score: 10\n";
+ File tmp = createTempFile("tmp", ".cfg");
+ Files.write(perfettoConfig.getBytes(StandardCharsets.UTF_8), tmp);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "install -m 644 -o root -g root "
+ + tmp.getAbsolutePath() + " /data/misc/perfetto-configs/bugreport-manager-test.cfg"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "perfetto --background-wait"
+ + " --config /data/misc/perfetto-configs/bugreport-manager-test.cfg --txt"
+ + " --out /data/misc/perfetto-traces/not-used.perfetto-trace"
+ );
+
+ // Legacy traces
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"cmd input_method tracing start"
);
@@ -563,19 +582,24 @@
return extractedFile;
}
- private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception {
+ private static void createFakeTraceFiles(Path[] paths) throws Exception {
File src = createTempFile("fake", ".data");
Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
for (Path path : paths) {
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "install -m 611 -o " + owner + " -g " + owner
- + " " + src.getAbsolutePath() + " " + path.toString()
+ "install -m 644 -o system -g system "
+ + src.getAbsolutePath() + " " + path.toString()
);
}
+
+ // Dumpstate executes "perfetto --save-for-bugreport" as shell
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "chown shell:shell /data/misc/perfetto-traces/bugreport/systrace.pftrace"
+ );
}
- private static List<File> copyFilesAsRoot(Path[] paths) throws Exception {
+ private static List<File> copyFiles(Path[] paths) throws Exception {
ArrayList<File> files = new ArrayList<File>();
for (Path src : paths) {
File dst = createTempFile(src.getFileName().toString(), ".copy");
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7f3e014..1577d9c1c1 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -857,7 +857,12 @@
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.getTertiaryFixedDimAccentColor(),
+ cNight.getTertiaryFixedDimAccentColor());
+ assertEquals(cDay.getOnTertiaryFixedAccentTextColor(),
+ cNight.getOnTertiaryFixedAccentTextColor());
assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -1830,7 +1835,9 @@
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.getTertiaryFixedDimAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getOnTertiaryFixedAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getRippleAlpha()).isAtLeast(0x00);
@@ -1846,9 +1853,12 @@
// These colors are only used for emphasized buttons; they do not need contrast
assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
+ assertContrastIsAtLeast(c.getTertiaryFixedDimAccentColor(), 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);
+ assertContrastIsAtLeast(c.getOnTertiaryFixedAccentTextColor(),
+ c.getTertiaryFixedDimAccentColor(), 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 0f62b1c..930b1a4 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.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.function.IntConsumer;
-
/**
* Tests for {@link ClientTransactionListenerController}.
*
@@ -47,30 +50,36 @@
@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);
+
+ mDisplayManager.registerDisplayListener(mListener, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
mController.onDisplayChanged(123);
+ mHandler.runWithScissors(() -> { }, 0);
- verify(mDisplayChangeListener).accept(123);
-
- clearInvocations(mDisplayChangeListener);
- mController.unregisterDisplayChangeListener(mDisplayChangeListener);
-
- mController.onDisplayChanged(321);
-
- verify(mDisplayChangeListener, never()).accept(anyInt());
+ verify(mListener).onDisplayChanged(123);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 531404b..d10cf16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,4 +62,24 @@
verify(callback2, times(1)).preExecute(clientTransactionHandler);
verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
}
+
+ @Test
+ public void testPreExecuteTransactionItems() {
+ final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ final ClientTransactionHandler clientTransactionHandler =
+ mock(ClientTransactionHandler.class);
+
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(clientTransactionHandler);
+
+ verify(callback1, times(1)).preExecute(clientTransactionHandler);
+ verify(callback2, times(1)).preExecute(clientTransactionHandler);
+ verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 44a4d58..f2b0f2e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,6 +247,31 @@
}
@Test
+ public void testExecuteTransactionItems_transactionResolution() {
+ ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
+ ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ IBinder token = mock(IBinder.class);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(mTransactionHandler);
+ mExecutor.execute(transaction);
+
+ InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+ inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
+ inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
+ @Test
public void testDoNotLaunchDestroyedActivity() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -279,12 +304,43 @@
}
@Test
+ public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
+ when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+ // Assume launch transaction is still in queue, so there is no client record.
+ when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+ // An incoming destroy transaction enters binder thread (preExecute).
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
+ destroyTransaction.addTransactionItem(
+ DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+ destroyTransaction.preExecute(mTransactionHandler);
+ // The activity should be added to to-be-destroyed container.
+ assertEquals(1, activitiesToBeDestroyed.size());
+
+ // A previous queued launch transaction runs on main thread (execute).
+ final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
+ final LaunchActivityItem launchItem =
+ spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+ launchTransaction.addTransactionItem(launchItem);
+ mExecutor.execute(launchTransaction);
+
+ // The launch transaction should not be executed because its token is in the
+ // to-be-destroyed container.
+ verify(launchItem, never()).execute(any(), any());
+
+ // After the destroy transaction has been executed, the token should be removed.
+ mExecutor.execute(destroyTransaction);
+ assertTrue(activitiesToBeDestroyed.isEmpty());
+ }
+
+ @Test
public void testActivityResultRequiredStateResolution() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
- IBinder token = mock(IBinder.class);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addCallback(postExecItem);
@@ -300,6 +356,26 @@
}
@Test
+ public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
+ PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(postExecItem);
+
+ // Verify resolution that should get to onPause
+ mClientRecord.setState(ON_RESUME);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+
+ // Verify resolution that should get to onStart
+ mClientRecord.setState(ON_STOP);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ }
+
+ @Test
public void testClosestStateResolutionForSameState() {
final int[] allStates = new int[] {
ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -444,6 +520,18 @@
mExecutor.executeCallbacks(transaction);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(activityItem);
+ when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+ mExecutor.executeTransactionItems(transaction);
+ }
+
@Test
public void testActivityItemExecute() {
final IBinder token = mock(IBinder.class);
@@ -464,6 +552,26 @@
inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
}
+ @Test
+ public void testExecuteTransactionItems_activityItemExecute() {
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ when(activityItem.getActivityToken()).thenReturn(token);
+ transaction.addTransactionItem(activityItem);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ transaction.addTransactionItem(stateRequest);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ mExecutor.execute(transaction);
+
+ final InOrder inOrder = inOrder(activityItem, stateRequest);
+ inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
private static int[] shuffledArray(int[] inputArray) {
final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 7d047c9..4aa62c5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,6 +285,10 @@
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(lifecycleRequest);
+
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
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 0000000..2ec58d4
--- /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 d8c9baa..0000000
--- 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/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3f78396..0d687b2 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,7 +16,7 @@
package android.graphics;
-import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -931,7 +931,7 @@
return String.format(xml, op, lang, font);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_prepend() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -947,7 +947,7 @@
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_replace() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -963,7 +963,7 @@
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_append() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -979,7 +979,7 @@
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -995,7 +995,7 @@
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index c2e6b60c..969ae8e 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.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 @@
@Mock
private DisplayManager.DisplayListener mListener;
+ @Mock
+ private DisplayManager.DisplayListener mListener2;
+
@Captor
private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;
@@ -82,7 +99,11 @@
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 @@
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/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
new file mode 100644
index 0000000..6acab36
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class ScrollFeedbackProviderTest {
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ @Test
+ public void testDefaultProviderType() {
+ View view = new View(mContext);
+
+ ScrollFeedbackProvider provider = ScrollFeedbackProvider.createProvider(view);
+
+ assertThat(provider).isInstanceOf(HapticScrollFeedbackProvider.class);
+ }
+
+ @Test
+ public void testDefaultProvider_createsDistinctProvidesOnMultipleCalls() {
+ View view1 = new View(mContext);
+ View view2 = new View(mContext);
+
+ ScrollFeedbackProvider view1Provider1 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view1Provider2 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view2Provider = ScrollFeedbackProvider.createProvider(view2);
+
+ assertThat(view1Provider1 == view1Provider2).isFalse();
+ assertThat(view1Provider1 == view2Provider).isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 06b62f8..dd116b5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -33,6 +33,7 @@
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -119,6 +120,11 @@
@Before
public void setUp() throws Exception {
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+ assumeFalse("AccessibilityShortcutChooserActivity not supported on watch",
+ pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
+
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.wakeUp();
when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
@@ -138,7 +144,9 @@
@After
public void cleanUp() {
- mScenario.close();
+ if (mScenario != null) {
+ mScenario.close();
+ }
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 8fa6376..77202d1 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -21,16 +21,12 @@
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -540,66 +536,4 @@
private void assertEquals(double expected, double actual) {
Assert.assertEquals(expected, actual, 0.1);
}
-
- @Test
- public void powerBrackets_specifiedInPowerProfile() {
- mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
- mProfile.initCpuPowerBrackets(8);
-
- int cpuPowerBracketCount = mProfile.getCpuPowerBracketCount();
- assertThat(cpuPowerBracketCount).isEqualTo(2);
- assertThat(new int[]{
- mProfile.getCpuPowerBracketForScalingStep(0, 0),
- mProfile.getCpuPowerBracketForScalingStep(4, 0),
- mProfile.getCpuPowerBracketForScalingStep(4, 1),
- }).isEqualTo(new int[]{1, 1, 0});
- }
-
- @Test
- public void powerBrackets_automatic() {
- mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
- CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
- new SparseArray<>() {{
- put(0, new int[]{0, 1, 2});
- put(3, new int[]{3, 4});
- }},
- new SparseArray<>() {{
- put(0, new int[]{300000, 1000000, 2000000});
- put(3, new int[]{300000, 1000000, 2500000, 3000000});
- }});
-
- assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(3);
- assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 0))
- .isEqualTo("0/300(10.0)");
- assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 1))
- .isEqualTo("0/1000(20.0), 0/2000(30.0), 3/300(25.0)");
- assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 2))
- .isEqualTo("3/1000(35.0), 3/2500(50.0), 3/3000(60.0)");
- assertThat(new int[]{
- mProfile.getCpuPowerBracketForScalingStep(0, 0),
- mProfile.getCpuPowerBracketForScalingStep(0, 1),
- mProfile.getCpuPowerBracketForScalingStep(0, 2),
- mProfile.getCpuPowerBracketForScalingStep(3, 0),
- mProfile.getCpuPowerBracketForScalingStep(3, 1),
- mProfile.getCpuPowerBracketForScalingStep(3, 2),
- mProfile.getCpuPowerBracketForScalingStep(3, 3),
- }).isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2});
- }
-
- @Test
- public void powerBrackets_moreBracketsThanStates() {
- mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
- mProfile.initCpuPowerBrackets(8);
-
- assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(7);
- assertThat(new int[]{
- mProfile.getCpuPowerBracketForScalingStep(0, 0),
- mProfile.getCpuPowerBracketForScalingStep(0, 1),
- mProfile.getCpuPowerBracketForScalingStep(0, 2),
- mProfile.getCpuPowerBracketForScalingStep(3, 0),
- mProfile.getCpuPowerBracketForScalingStep(3, 1),
- mProfile.getCpuPowerBracketForScalingStep(3, 2),
- mProfile.getCpuPowerBracketForScalingStep(3, 3),
- }).isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6});
- }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index cc73ece..35498b7 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 9fde0fd..92c4de6 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -27,6 +27,9 @@
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 @@
mCompatScaling = mInvCompatScaling = 1;
setTextLocales(LocaleList.getAdjustedDefault());
mColor = Color.pack(Color.BLACK);
+ resetElegantTextHeight();
}
/**
@@ -654,7 +658,7 @@
mBidiFlags = BIDI_DEFAULT_LTR;
setTextLocales(LocaleList.getAdjustedDefault());
- setElegantTextHeight(false);
+ resetElegantTextHeight();
mFontFeatureSettings = null;
mFontVariationSettings = null;
@@ -1735,12 +1739,30 @@
/**
* 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 @@
* @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);
+ }
}
/**
@@ -2113,6 +2157,31 @@
* The recommended additional space to add between lines of text.
*/
public float leading;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof FontMetrics)) return false;
+ FontMetrics that = (FontMetrics) o;
+ return that.top == top && that.ascent == ascent && that.descent == descent
+ && that.bottom == bottom && that.leading == leading;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(top, ascent, descent, bottom, leading);
+ }
+
+ @Override
+ public String toString() {
+ return "FontMetrics{"
+ + "top=" + top
+ + ", ascent=" + ascent
+ + ", descent=" + descent
+ + ", bottom=" + bottom
+ + ", leading=" + leading
+ + '}';
+ }
}
/**
@@ -2309,6 +2378,33 @@
*/
public int leading;
+ /**
+ * Set values from {@link FontMetricsInt}.
+ * @param fontMetricsInt a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetricsInt fontMetricsInt) {
+ top = fontMetricsInt.top;
+ ascent = fontMetricsInt.ascent;
+ descent = fontMetricsInt.descent;
+ bottom = fontMetricsInt.bottom;
+ leading = fontMetricsInt.leading;
+ }
+
+ /**
+ * Set values from {@link FontMetrics} with rounding accordingly.
+ * @param fontMetrics a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetrics fontMetrics) {
+ // See GraphicsJNI::set_metrics_int method for consistency.
+ top = (int) Math.floor(fontMetrics.top);
+ ascent = Math.round(fontMetrics.ascent);
+ descent = Math.round(fontMetrics.descent);
+ bottom = (int) Math.ceil(fontMetrics.bottom);
+ leading = Math.round(fontMetrics.leading);
+ }
+
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +
@@ -3608,9 +3704,9 @@
@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/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 6e04a2f..ba5628c 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,7 +182,7 @@
// For ignoring the customization, consume the new-locale-family element but don't
// register any customizations.
- if (com.android.text.flags.Flags.customLocaleFallback()) {
+ if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
outCustomization.add(new FontConfig.Customization.LocaleFallback(
Locale.forLanguageTag(lang), intOp, family));
}
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 4c75356..685fd82 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,7 +16,7 @@
package android.graphics.fonts;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -145,7 +145,7 @@
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public @Nullable FontFamily buildVariableFamily() {
int variableFamilyType = analyzeAndResolveVariableType(mFonts);
if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 618aa5b..3ef714ed 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -241,7 +241,7 @@
int configVersion
) {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
@@ -272,7 +272,7 @@
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index dc1773b..62195856 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 @@
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 @@
* </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 @@
@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 @@
* // 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 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 @@
* @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 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/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 569f9b6..7932e33 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,7 +16,7 @@
package android.graphics.text;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
@@ -173,7 +173,7 @@
* @param index the glyph index
* @return true if the fake bold option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeBold(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeBold(mLayoutPtr, index);
@@ -185,7 +185,7 @@
* @param index the glyph index
* @return true if the fake italic option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeItalic(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeItalic(mLayoutPtr, index);
@@ -195,7 +195,7 @@
* A special value returned by {@link #getWeightOverride(int)} and
* {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public static final float NO_OVERRIDE = Float.MIN_VALUE;
/**
@@ -205,7 +205,7 @@
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getWeightOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetWeightOverride(mLayoutPtr, index);
@@ -223,7 +223,7 @@
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getItalicOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetItalicOverride(mLayoutPtr, index);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 2f1eec1..7743ad5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -292,8 +292,8 @@
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct,
- containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+ false /* isolated */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -880,19 +880,17 @@
return true;
}
- // Skip resolving if the activity is on a pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
- final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
- if (container != null && taskContainer != null
- && taskContainer.isTaskFragmentContainerPinned(container)) {
+ // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+ if (container != null && container.isIsolatedNavigationEnabled()) {
return true;
}
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
if (!isOnReparent && taskContainer != null
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
// Do not resolve if the launched activity is not the top-most container (excludes
- // the pinned container) in the Task.
+ // the pinned and overlay container) in the Task.
return true;
}
@@ -1312,15 +1310,12 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
- final TaskContainer taskContainer =
- taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
- if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
- taskFragmentContainer)) {
+ if (taskFragmentContainer != null
+ && taskFragmentContainer.isIsolatedNavigationEnabled()) {
return null;
}
}
@@ -1756,31 +1751,6 @@
}
/**
- * Returns the topmost not finished container in Task of given task id.
- */
- @GuardedBy("mLock")
- @Nullable
- TaskFragmentContainer getTopActiveContainer(int taskId) {
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
- if (taskContainer == null) {
- return null;
- }
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
- for (int i = containers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = containers.get(i);
- if (!container.isFinished() && (container.getRunningActivityCount() > 0
- // We may be waiting for the top TaskFragment to become non-empty after
- // creation. In that case, we don't want to treat the TaskFragment below it as
- // top active, otherwise it may incorrectly launch placeholder on top of the
- // pending TaskFragment.
- || container.isWaitingActivityAppear())) {
- return container;
- }
- }
- return null;
- }
-
- /**
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
@@ -1793,6 +1763,15 @@
return;
}
+ if (container.isFinished()) {
+ return;
+ }
+
+ if (container.isOverlay()) {
+ updateOverlayContainer(wct, container);
+ return;
+ }
+
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1813,6 +1792,25 @@
updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
}
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ // Dismiss the overlay container if it's the only container in the task and there's no
+ // direct activity in the parent task.
+ if (taskContainer.getTaskFragmentContainers().size() == 1
+ && !taskContainer.hasDirectActivity()) {
+ container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+ }
+
+ // TODO(b/295805054): Add the logic to update overlay container
+ }
+
/**
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
@@ -1968,7 +1966,8 @@
/** Whether or not to allow activity in this container to launch placeholder. */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
- final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ final TaskFragmentContainer topContainer = container.getTaskContainer()
+ .getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
// The container is not the top most.
if (!container.isVisible()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 66e76c5..faf7c39 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -388,14 +388,27 @@
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
- !isStacked /* isolatedNav */);
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ */
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer taskFragmentContainer,
+ boolean isolatedNavigationEnabled) {
+ if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ return;
+ }
+ taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ isolatedNavigationEnabled);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index f4427aa..eeb3ccf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -75,6 +75,8 @@
private boolean mIsVisible;
+ private boolean mHasDirectActivity;
+
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
* the organizer receives
@@ -102,8 +104,9 @@
mConfiguration = taskProperties.getConfiguration();
mDisplayId = taskProperties.getDisplayId();
// Note that it is always called when there's a new Activity is started, which implies
- // the host task is visible.
+ // the host task is visible and has an activity in the task.
mIsVisible = true;
+ mHasDirectActivity = true;
}
int getTaskId() {
@@ -118,6 +121,10 @@
return mIsVisible;
}
+ boolean hasDirectActivity() {
+ return mHasDirectActivity;
+ }
+
@NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
@@ -127,6 +134,7 @@
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
mIsVisible = info.isVisible();
+ mHasDirectActivity = info.hasDirectActivity();
}
/**
@@ -191,11 +199,20 @@
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+ boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
if (!includePin && isTaskFragmentContainerPinned(container)) {
continue;
}
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 2ba5c9b..3e7f99b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -160,6 +160,9 @@
*/
private boolean mHasCrossProcessActivities;
+ /** Whether this TaskFragment enable isolated navigation. */
+ private boolean mIsIsolatedNavigationEnabled;
+
/**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
* TaskFragmentContainer, String)
@@ -805,6 +808,16 @@
mLastCompanionTaskFragment = fragmentToken;
}
+ /** Returns whether to enable isolated navigation or not. */
+ boolean isIsolatedNavigationEnabled() {
+ return mIsIsolatedNavigationEnabled;
+ }
+
+ /** Sets whether to enable isolated navigation or not. */
+ void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+ mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index af8017a..e74d5fb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -21,6 +21,7 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
@@ -57,6 +58,7 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -364,6 +366,81 @@
assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
}
+ @Test
+ public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ // Add a SplitPinContainer, the overlay should be on top
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity);
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+ final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+ .getSplitPinContainer().getSecondaryContainer();
+
+ // Add a normal container after the overlay, the overlay should still on top,
+ // and the SplitPinContainer should also on top of the normal one.
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertThat(taskContainer.getTaskFragmentContainers())
+ .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+ .inOrder();
+
+ assertWithMessage("The pinned container must be returned excluding the overlay")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+ .isEqualTo(topPinnedContainer);
+
+ assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+ .isEqualTo(container);
+
+ assertWithMessage("The overlay container must be returned since it's always on top")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+ false /* includePin */, true /* includeOverlay */))
+ .isEqualTo(overlayContainer);
+ }
+
+ @Test
+ public void testUpdateContainer_dontInvokeUpdateOverlayForNonOverlayContainer() {
+ TaskFragmentContainer taskFragmentContainer = createMockTaskFragmentContainer(mActivity);
+
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+ verify(mSplitController, never()).updateOverlayContainer(any(), any());
+ }
+
+ @Test
+ public void testUpdateOverlayContainer_dismissOverlayIfNeeded() {
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+ assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
+
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ assertWithMessage("The overlay must be dismissed since there's no activity"
+ + " in the task and other taskFragment.")
+ .that(taskContainer.getTaskFragmentContainers()).isEmpty();
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
@@ -375,6 +452,15 @@
taskId, mIntent, mActivity);
}
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
+ setupTaskFragmentInfo(container, activity);
+ return container;
+ }
+
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 6c0b3cf..02031a6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -48,13 +48,12 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -176,52 +175,6 @@
}
@Test
- public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = createTestTaskContainer();
- // tf1 has no running activity so is not active.
- final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
- // tf2 has running activity so is active.
- final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.addTaskFragmentContainer(tf2);
- // tf3 is finished so is not active.
- final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
- doReturn(true).when(tf3).isFinished();
- doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.addTaskFragmentContainer(tf3);
- mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
- assertWithMessage("Must return tf2 because tf3 is not active.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf3);
-
- assertWithMessage("Must return tf2 because tf2 has running activity.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf2);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(true).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
- + " creation.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- doReturn(false).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return null because tf1 becomes empty.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
- }
-
- @Test
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -306,7 +259,9 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+ TaskContainer taskContainer = tf.getTaskContainer();
+ spyOn(taskContainer);
+ verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
// Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -321,7 +276,7 @@
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer = mSplitController.getTaskContainer(TASK_ID);
taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -596,13 +551,12 @@
}
@Test
- public void testResolveStartActivityIntent_skipIfPinned() {
+ public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
- final TaskContainer taskContainer = container.getTaskContainer();
- spyOn(taskContainer);
+ container.setIsolatedNavigationEnabled(true);
+
final Intent intent = new Intent();
setupSplitRule(mActivity, intent);
- doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
mActivity));
}
@@ -1185,7 +1139,7 @@
public void testOnTransactionReady_taskFragmentParentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
- DEFAULT_DISPLAY, true);
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */);
transaction.addChange(new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(TASK_ID)
@@ -1569,9 +1523,9 @@
addSplitTaskFragments(primaryActivity, thirdActivity);
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
- assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
- assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
- == secondaryActivity);
+ assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+ assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+ .getTopNonFinishingActivity(), secondaryActivity);
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 2188996..e3f5169 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -79,14 +79,14 @@
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertEquals(WINDOWING_MODE_FREEFORM,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -106,13 +106,13 @@
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertFalse(taskContainer.isInPictureInPicture());
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertTrue(taskContainer.isInPictureInPicture());
}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 18796494..fd4522e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -172,4 +172,5 @@
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 7c0d0e3..51c71b1 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -20,3 +20,10 @@
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_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index fa56516..c525a29 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -35,8 +35,8 @@
<ImageView
android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_gravity="center_vertical"
android:contentDescription="@string/app_icon_text" />
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 ee9f070..c6f85a0 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"
@@ -33,10 +34,10 @@
<ImageView
android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginStart="14dp"
- android:layout_marginEnd="14dp"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
android:contentDescription="@string/app_icon_text"/>
<TextView
@@ -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/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1f6f7ae..c4be384 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -454,6 +454,9 @@
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
+ <!-- The radius of the caption menu icon. -->
+ <dimen name="desktop_mode_caption_icon_radius">28dp</dimen>
+
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 3790f04..d08c573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -70,7 +71,6 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -317,7 +317,11 @@
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.registerAnimation(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner)));
+ new BackAnimationRunner(
+ callback,
+ runner,
+ controller.mContext,
+ CUJ_PREDICTIVE_BACK_HOME)));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 431df21..dc413b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.annotation.NonNull;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
public class BackAnimationRunner {
+ private static final int NO_CUJ = -1;
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
+ private final @InteractionJankMonitor.CujType int mCujType;
+ private final Context mContext;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -45,9 +52,21 @@
private boolean mAnimationCancelled;
public BackAnimationRunner(
- @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context,
+ @InteractionJankMonitor.CujType int cujType) {
mCallback = callback;
mRunner = runner;
+ mCujType = cujType;
+ mContext = context;
+ }
+
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context) {
+ this(callback, runner, context, NO_CUJ);
}
/** Returns the registered animation runner */
@@ -70,10 +89,17 @@
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.endTracing(mCujType);
+ }
finishedCallback.run();
}
};
mWaitingAnimation = false;
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.beginTracing(
+ mCujType, mContext, apps[0].leash, /* tag */ null);
+ }
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
@@ -82,6 +108,10 @@
}
}
+ private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ return apps.length > 0 && mCujType != NO_CUJ;
+ }
+
void startGesture() {
mWaitingAnimation = true;
mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 114486e..24479d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -135,7 +136,8 @@
@Inject
public CrossActivityAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mBackground = background;
mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
mEnteringProgressSpring.setSpring(new SpringForce()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 209d853..fc5ff01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -20,6 +20,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.window.BackEvent.EDGE_RIGHT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -108,6 +109,7 @@
private final float[] mTmpTranslate = {0, 0, 0};
private final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
+ private final Context mContext;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -120,8 +122,10 @@
@Inject
public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ mContext = context;
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index aca638c..5254ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -97,7 +98,8 @@
SurfaceControl.Transaction transaction, Choreographer choreographer) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mCustomAnimationLoader = new CustomAnimationLoader(context);
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dddcbd4..f0da35d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -317,7 +317,8 @@
mBubbleIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
@@ -949,7 +950,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
@@ -988,7 +990,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index dc099d9..22e836a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -113,7 +113,7 @@
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(R.color.important_conversation),
+ res.getColor(com.android.launcher3.icons.R.color.important_conversation),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
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 ac5ba51e..3660fa2 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.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 @@
private ExpandedViewAnimationController mExpandedViewAnimationController;
private View mScrim;
- private boolean mScrimAnimating;
+ @Nullable
+ private ViewPropertyAnimator mScrimAnimation;
private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
@@ -748,8 +750,8 @@
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 @@
} 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 @@
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/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index de03f58..9cd318f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,7 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -515,7 +516,7 @@
}
if (backgroundColorForTransition != 0) {
- addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+ addBackgroundColor(info, backgroundColorForTransition, startTransaction,
finishTransaction);
}
@@ -546,7 +547,7 @@
return true;
}
- private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+ private void addBackgroundColor(@NonNull TransitionInfo info,
@ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
final Color bgColor = Color.valueOf(color);
@@ -558,9 +559,19 @@
.setName("animation-background")
.setCallsite("DefaultTransitionHandler")
.setColorLayer();
-
- mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+
+ // Attaching the background surface to the transition root could unexpectedly make it
+ // cover one of the split root tasks. To avoid this, put the background surface just
+ // above the display area when split is on.
+ final boolean isSplitTaskInvolved =
+ info.getChanges().stream().anyMatch(c-> c.getTaskInfo() != null
+ && c.getTaskInfo().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW);
+ if (isSplitTaskInvolved) {
+ mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+ } else {
+ startTransaction.reparent(backgroundSurface, info.getRootLeash());
+ }
startTransaction.setColor(backgroundSurface, colorArray)
.setLayer(backgroundSurface, -1)
.show(backgroundSurface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
new file mode 100644
index 0000000..f561aa5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -0,0 +1,119 @@
+/*
+ * 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving the home
+ * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ */
+public class HomeTransitionObserver implements TransitionObserver,
+ RemoteCallable<HomeTransitionObserver> {
+ private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ mListener;
+
+ private @NonNull final Context mContext;
+ private @NonNull final ShellExecutor mMainExecutor;
+ private @NonNull final Transitions mTransitions;
+
+ public HomeTransitionObserver(@NonNull Context context,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Transitions transitions) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mTransitions = transitions;
+
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> mTransitions.registerObserver(this),
+ c -> mTransitions.unregisterObserver(this));
+
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ final int mode = change.getMode();
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
+ && TransitionUtil.isOpenOrCloseMode(mode)) {
+ mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {}
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged,
+ @NonNull IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition,
+ boolean aborted) {}
+
+ /**
+ * Sets the home transition listener that receives any transitions resulting in a change of
+ *
+ */
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ if (listener != null) {
+ mListener.register(listener);
+ } else {
+ mListener.unregister();
+ }
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Invalidates this controller, preventing future calls to send updates.
+ */
+ public void invalidate() {
+ mTransitions.unregisterObserver(this);
+ }
+}
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
similarity index 60%
rename from core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 7a0f438..18716c6 100644
--- a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-package android.hardware.biometrics;
+package com.android.wm.shell.transition;
+
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
/**
- * Communication channel to propagate biometric prompt status. Implementation of this interface
- * should be registered in BiometricService#registerBiometricPromptStatusListener.
- * @hide
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
*/
-oneway interface IBiometricPromptStatusListener {
- void onBiometricPromptShowing();
- void onBiometricPromptIdle();
-}
\ No newline at end of file
+interface IHomeTransitionListener {
+
+ /**
+ * Called when a transition changes the visibility of the home activity.
+ */
+ void onHomeVisibilityChanged(in boolean isVisible);
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index cc4d268..644a6a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -19,6 +19,8 @@
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.transition.IHomeTransitionListener;
+
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
*/
@@ -39,4 +41,7 @@
* Retrieves the apply-token used by transactions in Shell
*/
IBinder getShellApplyToken() = 3;
+
+ /** Set listener that will receive callbacks about transitions involving home activity */
+ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ca2c3b4..293b660 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -146,13 +146,13 @@
});
}
};
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
try {
- // If the remote is actually in the same process, then make a copy of parameters since
- // remote impls assume that they have to clean-up native references.
- final SurfaceControl.Transaction remoteStartT =
- copyIfLocal(startTransaction, remote.getRemoteTransition());
- final TransitionInfo remoteInfo =
- remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
@@ -160,6 +160,10 @@
Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
+ if (remoteStartT != startTransaction) {
+ remoteStartT.close();
+ }
+ startTransaction.apply();
unhandleDeath(remote.asBinder(), finishCallback);
mRequestedRemotes.remove(transition);
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 576bba96..baa9aca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -356,7 +356,7 @@
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(this);
+ return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
}
@Override
@@ -1400,9 +1400,12 @@
private static class IShellTransitionsImpl extends IShellTransitions.Stub
implements ExternalInterfaceBinder {
private Transitions mTransitions;
+ private final HomeTransitionObserver mHomeTransitionObserver;
- IShellTransitionsImpl(Transitions transitions) {
+ IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
mTransitions = transitions;
+ mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
+ mTransitions);
}
/**
@@ -1410,6 +1413,7 @@
*/
@Override
public void invalidate() {
+ mHomeTransitionObserver.invalidate();
mTransitions = null;
}
@@ -1434,6 +1438,14 @@
public IBinder getShellApplyToken() {
return SurfaceControl.Transaction.getDefaultApplyToken();
}
+
+ @Override
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
+ (transitions) -> {
+ mHomeTransitionObserver.setHomeTransitionListener(listener);
+ });
+ }
}
private class SettingsObserver extends ContentObserver {
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 248e837..a976584 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
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
+import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -27,6 +29,7 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -44,6 +47,7 @@
import android.window.WindowContainerTransaction;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -93,7 +97,8 @@
private ResizeVeil mResizeVeil;
- private Drawable mAppIcon;
+ private Drawable mAppIconDrawable;
+ private Bitmap mAppIconBitmap;
private CharSequence mAppName;
private ExclusionRegionListener mExclusionRegionListener;
@@ -116,7 +121,8 @@
this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
DesktopModeWindowDecoration(
@@ -133,10 +139,12 @@
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
@@ -252,7 +260,7 @@
mOnCaptionButtonClickListener,
mOnCaptionLongClickListener,
mAppName,
- mAppIcon
+ mAppIconBitmap
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
@@ -359,10 +367,15 @@
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
try {
- IconProvider provider = new IconProvider(mContext);
- mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ final IconProvider provider = new IconProvider(mContext);
+ mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
PackageManager.ComponentInfoFlags.of(0)));
- ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+ final Resources resources = mContext.getResources();
+ final BaseIconFactory factory = new BaseIconFactory(mContext,
+ resources.getDisplayMetrics().densityDpi,
+ resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+ mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+ final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
@@ -383,7 +396,7 @@
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo,
+ mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo,
mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
}
@@ -452,26 +465,29 @@
}
/**
- * Create and display handle menu window
+ * Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
- .setAppIcon(mAppIcon)
+ .setAppIcon(mAppIconBitmap)
.setAppName(mAppName)
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.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 15f8f1c..1941d66 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
@@ -29,9 +29,9 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -58,7 +58,7 @@
private WindowDecoration.AdditionalWindow mHandleMenuWindow;
private final PointF mHandleMenuPosition = new PointF();
private final boolean mShouldShowWindowingPill;
- private final Drawable mAppIcon;
+ private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
private final View.OnTouchListener mOnTouchListener;
@@ -71,10 +71,13 @@
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) {
+ Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+ int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -83,9 +86,10 @@
mCaptionY = captionY;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
- mAppIcon = appIcon;
+ mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
}
@@ -98,6 +102,7 @@
ssg.addTransaction(t);
ssg.markSyncReady();
setupHandleMenu();
+ animateHandleMenu();
}
private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
@@ -109,6 +114,21 @@
}
/**
+ * 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.
*/
@@ -130,7 +150,7 @@
final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
final TextView appName = handleMenu.findViewById(R.id.application_name);
collapseBtn.setOnClickListener(mOnClickListener);
- appIcon.setImageDrawable(mAppIcon);
+ appIcon.setImageBitmap(mAppIconBitmap);
appName.setText(mAppName);
}
@@ -315,13 +335,14 @@
static final class Builder {
private final WindowDecoration mParent;
private CharSequence mName;
- private Drawable mAppIcon;
+ private Bitmap mAppIcon;
private View.OnClickListener mOnClickListener;
private View.OnTouchListener mOnTouchListener;
private int mLayoutId;
private int mCaptionX;
private int mCaptionY;
private boolean mShowWindowingPill;
+ private int mCaptionHeight;
Builder(@NonNull WindowDecoration parent) {
@@ -333,7 +354,7 @@
return this;
}
- Builder setAppIcon(@Nullable Drawable appIcon) {
+ Builder setAppIcon(@Nullable Bitmap appIcon) {
mAppIcon = appIcon;
return this;
}
@@ -364,9 +385,14 @@
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 0000000..531de1f
--- /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 d0e647b..634b755 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
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -137,7 +138,8 @@
Configuration windowDecorConfig) {
this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -145,17 +147,18 @@
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
+ @NonNull SurfaceControl taskSurface,
Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
- mTaskSurface = taskSurface;
+ mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
@@ -266,10 +269,10 @@
.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);
@@ -280,7 +283,7 @@
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,
@@ -345,7 +348,7 @@
// 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);
@@ -453,6 +456,7 @@
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
+ mTaskSurface.release();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -469,6 +473,13 @@
return resources.getDimension(resourceId);
}
+ private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+ Supplier<SurfaceControl> surfaceControlSupplier) {
+ final SurfaceControl copy = surfaceControlSupplier.get();
+ copy.copyFrom(sc, "WindowDecoration");
+ return copy;
+ }
+
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
@@ -558,6 +569,7 @@
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mCaptionHeight;
int mWidth;
int mHeight;
T mRootView;
@@ -565,6 +577,7 @@
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 6b59cce..d64312a 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
@@ -2,7 +2,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
+import android.graphics.Bitmap
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.view.View.OnLongClickListener
@@ -22,7 +22,7 @@
onCaptionButtonClickListener: View.OnClickListener,
onLongClickListener: OnLongClickListener,
appName: CharSequence,
- appIcon: Drawable
+ appIconBitmap: Bitmap
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -44,11 +44,10 @@
maximizeWindowButton.onLongClickListener = onLongClickListener
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
- appIconImageView.setImageDrawable(appIcon)
+ appIconImageView.setImageBitmap(appIconBitmap)
}
override fun bindData(taskInfo: RunningTaskInfo) {
-
val captionDrawable = captionView.background as GradientDrawable
taskInfo.taskDescription?.statusBarColor?.let {
captionDrawable.setColor(it)
@@ -63,6 +62,10 @@
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 9374ac9..9dc86db 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 @@
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 @@
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 @@
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 49e8d15..8b405f0 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 @@
}
} ?: 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/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 2cd08a4a..5965805 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -26,6 +26,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -66,8 +67,10 @@
standardAppHelper.launchViaIntent(
wmHelper,
NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
- ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
- NetflixAppHelper.WATCH_ACTIVITY)
+ ComponentNameMatcher(
+ NetflixAppHelper.PACKAGE_NAME,
+ NetflixAppHelper.WATCH_ACTIVITY
+ )
)
standardAppHelper.waitForVideoPlaying()
}
@@ -99,6 +102,41 @@
super.focusChanges()
}
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.statusBarLayerPositionAtEnd()
+ }
+
+ @Postsubmit
+ @Test override fun statusBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e7d0f60..d6141cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -568,8 +568,12 @@
}
private void registerAnimation(int type) {
- mController.registerAnimation(type,
- new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ mController.registerAnimation(
+ type,
+ new BackAnimationRunner(
+ mAnimatorCallback,
+ mBackAnimationRunner,
+ mContext));
}
private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index a2dbab1..18fcdd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -111,7 +111,8 @@
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, mMockSurfaceControlViewHostFactory);
+ WindowContainerTransaction::new, SurfaceControl::new,
+ mMockSurfaceControlViewHostFactory);
}
private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8061aa3..8e42f74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -121,6 +121,8 @@
private WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
private SurfaceSyncGroup mMockSurfaceSyncGroup;
+ @Mock
+ private SurfaceControl mMockTaskSurface;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -189,8 +191,7 @@
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -199,7 +200,7 @@
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -229,12 +230,11 @@
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -262,14 +262,15 @@
}
verify(mMockSurfaceControlFinishT)
- .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+ TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setWindowCrop(taskSurface, 300, 100);
- verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+ .setWindowCrop(mMockTaskSurface, 300, 100);
+ verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+ verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
- .show(taskSurface);
- verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+ .show(mMockTaskSurface);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -308,8 +309,7 @@
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -346,8 +346,7 @@
.setVisible(true)
.build();
- final TestWindowDecoration windowDecor =
- createWindowDecoration(taskInfo, new SurfaceControl());
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// It shouldn't show the window decoration when it can't obtain the display instance.
@@ -405,8 +404,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -465,8 +463,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -506,8 +503,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
@@ -545,12 +541,11 @@
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
mockitoSession.finishMocking();
}
@@ -568,8 +563,7 @@
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
.isVisible());
@@ -613,12 +607,11 @@
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).unsetColor(taskSurface);
+ verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
mockitoSession.finishMocking();
}
@@ -639,8 +632,7 @@
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -650,15 +642,15 @@
eq(0) /* index */, eq(mandatorySystemGestures()));
}
- private TestWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface, mWindowConfiguration,
+ taskInfo, mMockTaskSurface, mWindowConfiguration,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
- () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+ () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+ mMockSurfaceControlViewHostFactory);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -697,11 +689,12 @@
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
windowConfiguration, surfaceControlBuilderSupplier,
surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlSupplier, surfaceControlViewHostFactory);
}
@Override
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ffb329d..00d049c 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,6 +33,14 @@
#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/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index e672b98..e986c38 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -27,3 +27,10 @@
description: "APIs to create a new gainmap with a bitmap for metadata."
bug: "304478551"
}
+
+flag {
+ name: "clip_surfaceviews"
+ namespace: "core_graphics"
+ description: "Clip z-above surfaceviews to global clip rect"
+ bug: "298621623"
+}
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index bcfb4c8..7552b56d 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 @@
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 4a8f3e1..caffdfc 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -94,9 +94,10 @@
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 @@
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 1463945..8c71d6f 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -935,15 +935,39 @@
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 @@
{"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/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
, mTotalSize("bytes", 0)
, mPurgeableSize("bytes", 0) {}
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
for (auto& resource : mResourceMap) {
- if (SkStrContains(resourceName, resource.first)) {
+ if (resourceName.find(resource.first) != std::string::npos) {
return resource.second;
}
}
- return nullptr;
+ return std::nullopt;
}
void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@
}
// find the type if one exists
- const char* type;
+ std::string type;
auto typeResult = mCurrentValues.find("type");
if (typeResult != mCurrentValues.end()) {
type = typeResult->second.units;
@@ -71,14 +71,13 @@
}
// compute the type if we are itemizing or use the default "size" if we are not
- const char* key = (mItemizeType) ? type : sizeResult->first;
- SkASSERT(key != nullptr);
+ std::string key = (mItemizeType) ? type : sizeResult->first;
// compute the top level element name using either the map or category key
- const char* resourceName = mapName(mCurrentElement.c_str());
- if (mCategoryKey != nullptr) {
+ std::optional<std::string> resourceName = mapName(mCurrentElement);
+ if (mCategoryKey) {
// find the category if one exists
- auto categoryResult = mCurrentValues.find(mCategoryKey);
+ auto categoryResult = mCurrentValues.find(*mCategoryKey);
if (categoryResult != mCurrentValues.end()) {
resourceName = categoryResult->second.units;
} else if (mItemizeType) {
@@ -87,11 +86,11 @@
}
// if we don't have a pretty name then use the dumpName
- if (resourceName == nullptr) {
- resourceName = mCurrentElement.c_str();
+ if (!resourceName) {
+ resourceName = mCurrentElement;
}
- auto result = mResults.find(resourceName);
+ auto result = mResults.find(*resourceName);
if (result != mResults.end()) {
auto& resourceValues = result->second;
typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@
for (const auto& typedValue : namedItem.second) {
TraceValue traceValue = convertUnits(typedValue.second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
- log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
- traceValue.units, traceValue.count, entry);
+ log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
} else {
auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
}
}
@@ -156,7 +157,7 @@
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if ("bytes" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if ("KB" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b04..dbfc86b 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
#pragma once
-#include <SkString.h>
#include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
#include <utils/String8.h>
#include <unordered_map>
#include <vector>
@@ -60,17 +61,17 @@
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ std::string units;
float value;
int count;
};
- const char* mapName(const char* resourceName);
+ std::optional<std::string> mapName(const std::string& resourceName);
void processElement();
TraceValue convertUnits(const TraceValue& value);
const std::vector<ResourcePair> mResourceMap;
- const char* mCategoryKey = nullptr;
+ std::optional<std::string> mCategoryKey;
const bool mItemizeType;
// variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@
// variables storing information on the current node being dumped
std::string mCurrentElement;
- std::unordered_map<const char*, TraceValue> mCurrentValues;
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
// variable that stores the final format of the data after the individual elements are processed
- std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+ std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 14602ef..7fac0c9 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -562,7 +562,11 @@
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
diff --git a/location/api/current.txt b/location/api/current.txt
index 33effdd..0c23d8c 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,7 +412,9 @@
field public static final int TYPE_GPS_L1CA = 257; // 0x101
field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
- field public static final int TYPE_IRN_L5CA = 1793; // 0x701
+ field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
+ field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
+ field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index e28ad67..200d4ef 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -502,7 +502,7 @@
* <td colspan="4"><strong>BDS</strong></td>
* <td colspan="3"><strong>GAL</strong></td>
* <td><strong>SBAS</strong></td>
- * <td><strong>IRNSS</strong></td>
+ * <td><strong>NavIC</strong></td>
* </tr>
* <tr>
* <td><strong>State Flag</strong></td>
@@ -1528,17 +1528,17 @@
* <p>Similar to the Attribute field described in RINEX 4.00, e.g., in Tables 9-16 (see
* https://igs.org/wg/rinex/#documents-formats).
*
- * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS, GLONASS G1a L1OCd,
+ * <p>Returns "A" for GALILEO E1A, GALILEO E6A, NavIC L5A SPS, NavIC SA SPS, GLONASS G1a L1OCd,
* GLONASS G2a L2CSI.
*
- * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D), IRNSS SB RS (D), GLONASS G1a
+ * <p>Returns "B" for GALILEO E1B, GALILEO E6B, NavIC L5B RS (D), NavIC SB RS (D), GLONASS G1a
* L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
*
* <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
- * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P), IRNSS SC RS (P).
+ * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, NavIC L5C RS (P), NavIC SC RS (P).
*
* <p>Returns "D" for GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)), QZSS L5S(I), BDS B1C Data,
- * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+ * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data, NavIC L1 Data.
*
* <p>Returns “E” for QZSS L1 C/B, QZSS L6E.
*
@@ -1553,7 +1553,7 @@
* <p>Returns "N" for GPS L1 codeless, GPS L2 codeless.
*
* <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C Pilot, BDS B2a Pilot,
- * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q).
+ * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q), NavIC L1 Pilot.
*
* <p>Returns "Q" for GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q,
* SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q.
@@ -1567,7 +1567,8 @@
* GLONASS G2a L2CSI+L2OCp, GLONASS G3 (I+Q), GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO
* E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C
* (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q), BDS B1C Data+Pilot, BDS B2a Data+Pilot,
- * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
+ * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), NavIC L5 (B+C), NavIC S (B+C),
+ * NavIC L1 Data+Pilot.
*
* <p>Returns "Y" for GPS L1Y, GPS L2Y.
*
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 637f905..32e636f 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -16,10 +16,12 @@
package android.location;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,7 +43,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2,
TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1,
- TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA})
+ TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA, TYPE_IRN_L5, TYPE_IRN_L1})
public @interface GnssNavigationMessageType {}
// The following enumerations must be in sync with the values declared in gps.h
@@ -74,8 +76,18 @@
public static final int TYPE_GAL_I = 0x0601;
/** Galileo F/NAV message contained in the structure. */
public static final int TYPE_GAL_F = 0x0602;
- /** IRNSS L5 C/A message contained in the structure. */
+ /**
+ * NavIC L5 C/A message contained in the structure.
+ * @deprecated Use {@link #TYPE_IRN_L5} instead.
+ */
+ @Deprecated
public static final int TYPE_IRN_L5CA = 0x0701;
+ /** NavIC L5 message contained in the structure. */
+ @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+ public static final int TYPE_IRN_L5 = 0x0702;
+ /** NavIC L1 message contained in the structure. */
+ @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+ public static final int TYPE_IRN_L1 = 0x0703;
/**
* The status of the GNSS Navigation Message
@@ -254,8 +266,15 @@
case TYPE_GAL_F:
return "Galileo F";
case TYPE_IRN_L5CA:
- return "IRNSS L5 C/A";
+ return "NavIC L5 C/A";
default:
+ if (Flags.gnssApiNavicL1()) {
+ if (mType == TYPE_IRN_L5) {
+ return "NavIC L5";
+ } else if (mType == TYPE_IRN_L1) {
+ return "NavIC L1";
+ }
+ }
return "<Invalid:" + mType + ">";
}
}
@@ -303,9 +322,12 @@
* navigation message, in the range of 1-25 (Subframe 1, 2, 3 does not contain a 'frame id' and
* this value can be set to -1.)</li>
* <li> For Beidou CNAV1 this refers to the page type number in the range of 1-63.</li>
- * <li> For IRNSS L5 C/A subframe 3 and 4, this value corresponds to the Message Id of the
+ * <li> For NavIC L5 subframe 3 and 4, this value corresponds to the Message Id of the
* navigation message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type
* id and this value can be set to -1.)</li>
+ * <li> For NavIC L1 subframe 3, this value corresponds to the Message Id of the navigation
+ * message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type id and this
+ * value can be set to -1.)</li>
* </ul>
*/
@IntRange(from = -1, to = 120)
@@ -339,8 +361,10 @@
* navigation message, in the range of 1-3.</li>
* <li> For Beidou CNAV2, the submessage id corresponds to the message type, in the range
* 1-63.</li>
- * <li> For IRNSS L5 C/A, the submessage id corresponds to the subframe number of the
- * navigation message, in the range of 1-4.</li>
+ * <li> For NavIC L5, the submessage id corresponds to the subframe number of the navigation
+ * message, in the range of 1-4.</li>
+ * <li> For NavIC L1, the submessage id corresponds to the subframe number of the navigation
+ * message, in the range of 1-3.</li>
* </ul>
*/
@IntRange(from = 1)
@@ -363,7 +387,7 @@
* <p>The bytes (or words) specified using big endian format (MSB first).
*
* <ul>
- * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 & Beidou D2, each subframe contains 10
+ * <li>For GPS L1 C/A, NavIC L5, Beidou D1 & Beidou D2, each subframe contains 10
* 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip
* B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and
* 0.6 seconds, respectively.</li>
@@ -383,6 +407,9 @@
* 75 bytes. subframe #3 consists of 264 data bits that should be fit into 33 bytes.</li>
* <li>For Beidou CNAV2, each subframe consists of 288 data bits, that should be fit into 36
* bytes.</li>
+ * <li> For NavIC L1, subframe #1 consists of 9 data bits that should be fit into 2 bytes (skip
+ * B10-B16). subframe #2 consists of 600 bits that should be fit into 75 bytes. subframe #3
+ * consists of 274 data bits that should be fit into 35 bytes (skip B275-B280).</li>
* </ul>
*/
@NonNull
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
new file mode 100644
index 0000000..c471a27
--- /dev/null
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -0,0 +1,8 @@
+package: "android.location.flags"
+
+flag {
+ name: "gnss_api_navic_l1"
+ namespace: "location"
+ description: "Flag for GNSS API for NavIC L1"
+ bug: "302199306"
+}
\ No newline at end of file
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index af3c295..5a27435 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -68,7 +68,7 @@
/**
* 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 @@
/**
* @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 @@
/**
* @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 5b88079..9ad5c3e 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.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 @@
* @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 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 0e7718b..8584dbc 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -498,6 +498,10 @@
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/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 76a00ac..f68eb3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -52,6 +52,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -383,9 +384,9 @@
* @see #setRouteListingPreference(RouteListingPreference)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void registerRouteListingPreferenceCallback(
+ public void registerRouteListingPreferenceUpdatedCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
@@ -398,15 +399,20 @@
/**
* Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+ *
+ * @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void unregisterRouteListingPreferenceCallback(
- @NonNull RouteListingPreferenceCallback callback) {
+ public void unregisterRouteListingPreferenceUpdatedCallback(
+ @NonNull Consumer<RouteListingPreference> callback) {
Objects.requireNonNull(callback, "callback must not be null");
if (!mListingPreferenceCallbackRecords.remove(
new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
- Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
+ Log.w(
+ TAG,
+ "unregisterRouteListingPreferenceUpdatedCallback: Ignoring an unknown"
+ + " callback");
}
}
@@ -1119,9 +1125,7 @@
private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
record.mExecutor.execute(
- () ->
- record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
- preference));
+ () -> record.mRouteListingPreferenceCallback.accept(preference));
}
}
@@ -1208,22 +1212,6 @@
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
- /** Callback for receiving events related to {@link RouteListingPreference}. */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public abstract static class RouteListingPreferenceCallback {
-
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public RouteListingPreferenceCallback() {}
-
- /**
- * Called when the {@link RouteListingPreference} changes.
- *
- * @see #getRouteListingPreference
- */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
- }
-
/** Callback for receiving events on media transfer. */
public abstract static class TransferCallback {
/**
@@ -1774,11 +1762,11 @@
private static final class RouteListingPreferenceCallbackRecord {
public final Executor mExecutor;
- public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+ public final Consumer<RouteListingPreference> mRouteListingPreferenceCallback;
/* package */ RouteListingPreferenceCallbackRecord(
@NonNull Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
mExecutor = executor;
mRouteListingPreferenceCallback = routeListingPreferenceCallback;
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c9cfa67..386534b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -34,3 +34,10 @@
description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
bug: "293743975"
}
+
+flag {
+ namespace: "media_solutions"
+ name: "enable_waiting_state_for_system_session_creation_request"
+ description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
+ bug: "307723189"
+}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d0e860c..24efbd1 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -158,26 +158,6 @@
in @nullable IMediaProjection projection);
/**
- * Notifies system server that we are handling a particular state during the consent flow.
- *
- * <p>Only used for emitting atoms.
- *
- * @param hostUid The uid of the process requesting consent to capture, may be an app or
- * SystemUI.
- * @param state The state that SystemUI is handling during the consent flow.
- * Must be a valid
- * state defined in the MediaProjectionState enum.
- * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
- * Indicates the entry point for requesting the permission. Must be
- * a valid state defined
- * in the SessionCreationSource enum.
- */
- @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
- @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
- + ".permission.MANAGE_MEDIA_PROJECTION)")
- oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
-
- /**
* Notifies system server that the permission request was initiated.
*
* <p>Only used for emitting atoms.
@@ -193,4 +173,43 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
+
+ /**
+ * Notifies system server that the permission request was displayed.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestDisplayed(int hostUid);
+
+ /**
+ * Notifies system server that the permission request was cancelled.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestCancelled(int hostUid);
+
+ /**
+ * Notifies system server that the app selector was displayed.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyAppSelectorDisplayed(int hostUid);
}
diff --git a/nfc-extras/OWNERS b/nfc-extras/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc-extras/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index ffed804..fe8386e 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -16,6 +16,8 @@
package com.android.nfc_extras;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.HashMap;
import android.content.Context;
@@ -30,6 +32,8 @@
*
* There is a 1-1 relationship between an {@link NfcAdapterExtras} object and
* a {@link NfcAdapter} object.
+ *
+ * TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
*/
public final class NfcAdapterExtras {
private static final String TAG = "NfcAdapterExtras";
@@ -72,15 +76,40 @@
private final NfcAdapter mAdapter;
final String mPackageName;
+ private static INfcAdapterExtras
+ getNfcAdapterExtrasInterfaceFromNfcAdapter(NfcAdapter adapter) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod("getNfcAdapterExtrasInterface");
+ method.setAccessible(true);
+ return (INfcAdapterExtras) method.invoke(adapter);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+ Log.e(TAG, "Unable to get context from NfcAdapter");
+ }
+ return null;
+ }
+
/** get service handles */
private static void initService(NfcAdapter adapter) {
- final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface();
+ final INfcAdapterExtras service = getNfcAdapterExtrasInterfaceFromNfcAdapter(adapter);
if (service != null) {
// Leave stale rather than receive a null value.
sService = service;
}
}
+ private static Context getContextFromNfcAdapter(NfcAdapter adapter) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod("getContext");
+ method.setAccessible(true);
+ return (Context) method.invoke(adapter);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+ Log.e(TAG, "Unable to get context from NfcAdapter");
+ }
+ return null;
+ }
+
/**
* Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}.
*
@@ -91,7 +120,7 @@
* @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter}
*/
public static NfcAdapterExtras get(NfcAdapter adapter) {
- Context context = adapter.getContext();
+ Context context = getContextFromNfcAdapter(adapter);
if (context == null) {
throw new UnsupportedOperationException(
"You must pass a context to your NfcAdapter to use the NFC extras APIs");
@@ -112,7 +141,7 @@
private NfcAdapterExtras(NfcAdapter adapter) {
mAdapter = adapter;
- mPackageName = adapter.getContext().getPackageName();
+ mPackageName = getContextFromNfcAdapter(adapter).getPackageName();
mEmbeddedEe = new NfcExecutionEnvironment(this);
mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON,
mEmbeddedEe);
@@ -156,12 +185,24 @@
}
}
+ private static void attemptDeadServiceRecoveryOnNfcAdapter(NfcAdapter adapter, Exception e) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod(
+ "attemptDeadServiceRecovery", Exception.class);
+ method.setAccessible(true);
+ method.invoke(adapter, e);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException ex) {
+ Log.e(TAG, "Unable to attempt dead service recovery on NfcAdapter");
+ }
+ }
+
/**
* NFC service dead - attempt best effort recovery
*/
void attemptDeadServiceRecovery(Exception e) {
Log.e(TAG, "NFC Adapter Extras dead - attempting to recover");
- mAdapter.attemptDeadServiceRecovery(e);
+ attemptDeadServiceRecoveryOnNfcAdapter(mAdapter, e);
initService(mAdapter);
}
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 30d1773..4e4894c 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -27,7 +27,7 @@
<string name="passkey_creation_intro_title" msgid="4251037543787718844">"Geçiş anahtarlarıyla daha yüksek güvenlik"</string>
<string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Geçiş anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
<string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Geçiş anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
- <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için şifre anahtarları bir şifre yöneticisine kaydedilir"</string>
+ <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için geçiş anahtarları bir şifre yöneticisine kaydedilir"</string>
<string name="more_about_passkeys_title" msgid="7797903098728837795">"Geçiş anahtarları hakkında daha fazla bilgi"</string>
<string name="passwordless_technology_title" msgid="2497513482056606668">"Şifresiz teknoloji"</string>
<string name="passwordless_technology_detail" msgid="6853928846532955882">"Geçiş anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp geçiş anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
@@ -39,10 +39,10 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
- <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre anahtarı oluşturulsun mu?"</string>
+ <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için geçiş anahtarı oluşturulsun mu?"</string>
<string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre kaydedilsin mi?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
- <string name="passkey" msgid="632353688396759522">"Şifre anahtarı"</string>
+ <string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
<string name="password" msgid="6738570945182936667">"Şifre"</string>
<string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
<string name="passwords" msgid="5419394230391253816">"şifreler"</string>
@@ -61,14 +61,14 @@
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> geçiş anahtarı"</string>
<string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> kimlik bilgileri"</string>
- <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Geçiş anahtarı"</string>
<string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
<string name="accessibility_close_button" msgid="1163435587545377687">"Kapat"</string>
<string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Kapat"</string>
- <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı geçiş anahtarınız kullanılsın mı?"</string>
<string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifreniz kullanılsın mı?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileriniz kullanılsın mı?"</string>
<string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma seçeneklerine izin verilsin mi?"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 222b865..495abe6 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -53,7 +53,7 @@
<string name="save_password_on_other_device_title" msgid="5829084591948321207">"在其他设备上保存密码?"</string>
<string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"在其他设备上保存登录凭据?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
- <string name="use_provider_for_all_description" msgid="1998772715863958997">"此 <xliff:g id="USERNAME">%1$s</xliff:g> 密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
+ <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> 的这个密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
<string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
<string name="settings" msgid="6536394145760913145">"设置"</string>
<string name="use_once" msgid="9027366575315399714">"使用一次"</string>
diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp
index 0d4af2a..47ca944 100644
--- a/packages/CredentialManager/shared/Android.bp
+++ b/packages/CredentialManager/shared/Android.bp
@@ -16,5 +16,6 @@
"androidx.core_core-ktx",
"androidx.credentials_credentials",
"guava",
+ "hilt_android",
],
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
index 1cce3ba..5738fee 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
@@ -25,8 +25,11 @@
import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.Password
import com.android.credentialmanager.model.Request
+import javax.inject.Inject
+import javax.inject.Singleton
-class PasswordRepository {
+@Singleton
+class PasswordRepository @Inject constructor() {
suspend fun selectPassword(
password: Password,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
index 5ab5ab9..1973fc1 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt
@@ -16,17 +16,20 @@
package com.android.credentialmanager.repository
-import android.app.Application
import android.content.Intent
+import android.content.pm.PackageManager
import android.util.Log
import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.parse
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
-class RequestRepository(
- private val application: Application,
+@Singleton
+class RequestRepository @Inject constructor(
+ private val packageManager: PackageManager,
) {
private val _requests = MutableStateFlow<Request?>(null)
@@ -34,7 +37,7 @@
suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) {
val request = intent.parse(
- packageManager = application.packageManager,
+ packageManager = packageManager,
previousIntent = previousIntent
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 477e61d..f0fa6c5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -336,7 +336,10 @@
return result
}
- private fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? {
+ /**
+ * @hide
+ */
+ fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? {
try {
when (slice.spec?.type) {
TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!!
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 9355517..81cbd5a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -23,6 +23,8 @@
import android.credentials.GetCandidateCredentialsException
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCredentialRequest
+import android.credentials.ui.GetCredentialProviderData
+import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
@@ -42,7 +44,13 @@
import org.json.JSONException
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.credentials.provider.CustomCredentialEntry
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.PublicKeyCredentialEntry
import com.android.credentialmanager.GetFlowUtils
+import com.android.credentialmanager.getflow.CredentialEntryInfo
+import com.android.credentialmanager.getflow.ProviderDisplayInfo
+import com.android.credentialmanager.getflow.toProviderDisplayInfo
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -107,6 +115,34 @@
)
}
+ private fun getEntryToIconMap(
+ candidateProviderDataList: MutableList<GetCredentialProviderData>
+ ): Map<String, Icon> {
+ val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
+ candidateProviderDataList.forEach { provider ->
+ provider.credentialEntries.forEach { entry ->
+ val credentialEntry = GetFlowUtils.parseCredentialEntryFromSlice(entry.slice)
+ when (credentialEntry) {
+ is PasswordCredentialEntry -> {
+ entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
+ }
+ is PublicKeyCredentialEntry -> {
+ entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
+ }
+ is CustomCredentialEntry -> {
+ entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
+ }
+ }
+ }
+ }
+ return entryIconMap
+ }
+
+ private fun getDefaultIcon(): Icon {
+ return Icon.createWithResource(
+ this, com.android.credentialmanager.R.drawable.ic_other_sign_in_24)
+ }
+
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
filLRequest: FillRequest
@@ -114,10 +150,16 @@
val providerList = GetFlowUtils.toProviderList(
getCredResponse.candidateProviderDataList,
this@CredentialAutofillService)
+ if (providerList.isEmpty()) {
+ return null
+ }
+ val entryIconMap: Map<String, Icon> =
+ getEntryToIconMap(getCredResponse.candidateProviderDataList)
var totalEntryCount = 0
providerList.forEach { provider ->
totalEntryCount += provider.credentialEntryList.size
}
+ val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList)
val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
@@ -129,15 +171,30 @@
var i = 0
val fillResponseBuilder = FillResponse.Builder()
var emptyFillResponse = true
- providerList.forEach {provider ->
- // TODO(b/299321128): Before iterating the list, sort the list so that
- // the relevant entries don't get truncated
- provider.credentialEntryList.forEach entryLoop@ {entry ->
- val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- AutofillId::class.java)
- val pendingIntent = entry.pendingIntent
+
+ providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+ val primaryEntry = it.sortedCredentialEntryList.first()
+ // In regular CredMan bottomsheet, only one primary entry per username is displayed.
+ // But since the credential requests from different fields are allocated into a single
+ // request for autofill, there will be duplicate primary entries, especially for
+ // username/pw autofill fields. These primary entries will be the same entries except
+ // their autofillIds will point to different autofill fields. Process all primary
+ // fields.
+ // TODO(b/307435163): Merge credential options
+ it.sortedCredentialEntryList.forEach entryLoop@ { credentialEntry ->
+ if (!isSameCredentialEntry(primaryEntry, credentialEntry)) {
+ // Encountering different credential entry means all the duplicate primary
+ // entries have been processed.
+ return@usernameLoop
+ }
+ val autofillId: AutofillId? = credentialEntry
+ .fillInIntent
+ ?.getParcelableExtra(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ AutofillId::class.java)
+ val pendingIntent = credentialEntry.pendingIntent
if (autofillId == null || pendingIntent == null) {
+ Log.e(TAG, "AutofillId or pendingIntent was missing from the entry.")
return@entryLoop
}
var inlinePresentation: InlinePresentation? = null
@@ -151,7 +208,11 @@
}
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
- .setTitle(entry.userName)
+ .setTitle(credentialEntry.userName)
+ val icon: Icon =
+ entryIconMap[credentialEntry.entryKey + credentialEntry.entrySubkey]
+ ?: getDefaultIcon()
+ sliceBuilder.setStartIcon(icon)
inlinePresentation = InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ false)
}
@@ -169,8 +230,8 @@
Field.Builder().setPresentations(
presentationBuilder.build())
.build())
- .setAuthentication(entry.pendingIntent.intentSender)
- .setAuthenticationExtras(entry.fillInIntent.extras)
+ .setAuthentication(pendingIntent.intentSender)
+ .setAuthenticationExtras(credentialEntry.fillInIntent.extras)
.build())
emptyFillResponse = false
}
@@ -292,4 +353,15 @@
}
return result
}
+
+ private fun isSameCredentialEntry(
+ info1: CredentialEntryInfo,
+ info2: CredentialEntryInfo
+ ): Boolean {
+ return info1.providerId == info2.providerId &&
+ info1.lastUsedTimeMillis == info2.lastUsedTimeMillis &&
+ info1.credentialType == info2.credentialType &&
+ info1.displayName == info2.displayName &&
+ info1.userName == info2.userName
+ }
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 716f474..447a9d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -203,9 +203,14 @@
UNLOCKED_AUTH_ENTRIES_ONLY,
}
-// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
-// entry exists
-private fun toProviderDisplayInfo(
+
+/**
+ * IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
+ * entry exists
+ *
+ * @hide
+ */
+fun toProviderDisplayInfo(
providerInfoList: List<ProviderInfo>
): ProviderDisplayInfo {
val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
index e5f5cc2..c883b1f2 100644
--- a/packages/CredentialManager/wear/Android.bp
+++ b/packages/CredentialManager/wear/Android.bp
@@ -22,6 +22,7 @@
static_libs: [
"CredentialManagerShared",
+ "hilt_android",
"Horologist",
"PlatformComposeCore",
"androidx.activity_activity-compose",
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 273d0b1..0a63cb7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -29,13 +29,13 @@
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.belowTimeTextPreview
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
-class CredentialSelectorActivity : ComponentActivity() {
+@AndroidEntryPoint(ComponentActivity::class)
+class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
- private val viewModel: CredentialSelectorViewModel by viewModels {
- CredentialSelectorViewModel.Factory
- }
+ private val viewModel: CredentialSelectorViewModel by viewModels()
@OptIn(ExperimentalHorologistApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
index e8e4033..6bd166e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -17,18 +17,7 @@
package com.android.credentialmanager
import android.app.Application
-import com.android.credentialmanager.di.inject
-import com.android.credentialmanager.repository.PasswordRepository
-import com.android.credentialmanager.repository.RequestRepository
+import dagger.hilt.android.HiltAndroidApp
-class CredentialSelectorApp : Application() {
-
- lateinit var requestRepository: RequestRepository
- lateinit var passwordRepository: PasswordRepository
-
- override fun onCreate() {
- super.onCreate()
-
- inject()
- }
-}
\ No newline at end of file
+@HiltAndroidApp(Application::class)
+class CredentialSelectorApp : Hilt_CredentialSelectorApp()
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index d557dc0..435cd37 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,20 +18,20 @@
import android.content.Intent
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.CreationExtras
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.repository.RequestRepository
import com.android.credentialmanager.ui.mappers.toGet
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class CredentialSelectorViewModel(
+@HiltViewModel
+class CredentialSelectorViewModel @Inject constructor(
private val requestRepository: RequestRepository,
) : ViewModel() {
@@ -56,22 +56,6 @@
requestRepository.processRequest(intent = intent, previousIntent = previousIntent)
}
}
-
- companion object {
- val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val application = checkNotNull(extras[APPLICATION_KEY])
-
- return CredentialSelectorViewModel(
- requestRepository = (application as CredentialSelectorApp).requestRepository,
- ) as T
- }
- }
- }
}
sealed class CredentialSelectorUiState {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
new file mode 100644
index 0000000..cb1a4a1
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
@@ -0,0 +1,18 @@
+package com.android.credentialmanager.di
+
+import android.content.Context
+import android.content.pm.PackageManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+@Module
+@InstallIn(SingletonComponent::class)
+internal object AppModule {
+ @Provides
+ @JvmStatic
+ fun providePackageManager(@ApplicationContext context: Context): PackageManager =
+ context.packageManager
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
deleted file mode 100644
index 1e8f83d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.credentialmanager.di
-
-import android.app.Application
-import com.android.credentialmanager.CredentialSelectorApp
-import com.android.credentialmanager.repository.PasswordRepository
-import com.android.credentialmanager.repository.RequestRepository
-
-// TODO b/301601582 add Hilt for dependency injection
-
-fun CredentialSelectorApp.inject() {
- requestRepository = requestRepository(application = this)
- passwordRepository = passwordRepository()
-}
-
-private fun requestRepository(
- application: Application,
-): RequestRepository = RequestRepository(
- application = application,
-)
-
-private fun passwordRepository(): PasswordRepository = PasswordRepository()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index c87cfd3..81a0672 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -47,8 +47,7 @@
columnState: ScalingLazyColumnState,
onCloseApp: () -> Unit,
modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel =
- viewModel(factory = SinglePasswordScreenViewModel.Factory),
+ viewModel: SinglePasswordScreenViewModel = viewModel(),
) {
viewModel.initialize()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 3167e67..43514a0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -21,11 +21,7 @@
import androidx.activity.result.IntentSenderRequest
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.CreationExtras
-import com.android.credentialmanager.CredentialSelectorApp
import com.android.credentialmanager.TAG
import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.model.Password
@@ -33,12 +29,15 @@
import com.android.credentialmanager.repository.PasswordRepository
import com.android.credentialmanager.repository.RequestRepository
import com.android.credentialmanager.ui.model.PasswordUiModel
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class SinglePasswordScreenViewModel(
+@HiltViewModel
+class SinglePasswordScreenViewModel @Inject constructor(
private val requestRepository: RequestRepository,
private val passwordRepository: PasswordRepository,
) : ViewModel() {
@@ -105,23 +104,6 @@
_uiState.value = SinglePasswordScreenUiState.Completed
}
}
-
- companion object {
- val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val application = checkNotNull(extras[APPLICATION_KEY])
-
- return SinglePasswordScreenViewModel(
- requestRepository = (application as CredentialSelectorApp).requestRepository,
- passwordRepository = application.passwordRepository,
- ) as T
- }
- }
- }
}
sealed class SinglePasswordScreenUiState {
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 8975857..47ce587 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -83,7 +83,7 @@
android:id="@android:id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="4dp"
android:layout_marginTop="4dp"
android:max="100"
android:visibility="gone"/>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 155cfbb..b633337 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -173,13 +173,12 @@
return mCoordinatorLayout;
}
- /** Sets the title on the collapsing layout, delegating to host if needed. */
+ /** Sets the title on the collapsing layout and delegates to host. */
public void setTitle(CharSequence title) {
if (mCollapsingToolbarLayout != null) {
mCollapsingToolbarLayout.setTitle(title);
- } else {
- mHostCallback.setOuterTitle(title);
}
+ mHostCallback.setOuterTitle(title);
}
/** Returns an instance of collapsing toolbar. */
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index f1e028b..6979825 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -25,6 +25,7 @@
<item name="editTextPreferenceStyle">@style/SettingsEditTextPreference.SettingsLib</item>
<item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference.SettingsLib</item>
<item name="switchPreferenceStyle">@style/SettingsSwitchPreference.SettingsLib</item>
+ <item name="switchPreferenceCompatStyle">@style/SettingsSwitchPreferenceCompat.SettingsLib</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference.SettingsLib</item>
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
@@ -67,6 +68,11 @@
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
+ <style name="SettingsSwitchPreferenceCompat.SettingsLib" parent="@style/Preference.SwitchPreferenceCompat.Material">
+ <item name="layout">@layout/settingslib_preference</item>
+ <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+ </style>
+
<style name="SettingsSeekbarPreference.SettingsLib" parent="@style/Preference.SeekBarPreference.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index c4b6047..f44b161 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -34,12 +34,17 @@
</style>
<style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
- <item name="android:switchMinWidth">52dp</item>
<item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
<item name="android:track">@drawable/settingslib_switch_track</item>
<item name="android:thumb">@drawable/settingslib_switch_thumb</item>
</style>
+ <style name="SwitchCompat.SettingsLib" parent="@style/Widget.AppCompat.CompoundButton.Switch">
+ <item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
+ <item name="track">@drawable/settingslib_switch_track</item>
+ <item name="android:thumb">@drawable/settingslib_switch_thumb</item>
+ </style>
+
<style name="HorizontalProgressBar.SettingsLib"
parent="android:style/Widget.Material.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 69c122c..698f21d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -25,6 +25,7 @@
<item name="android:listPreferredItemPaddingRight">16dp</item>
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
+ <item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
</style>
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index eaeda3c..009407a 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -38,6 +38,7 @@
static_libs: [
"androidx.compose.runtime_runtime",
"SpaPrivilegedLib",
+ "android.content.pm.flags-aconfig-java",
],
kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index a428142..d95dd8c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,6 +17,8 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -65,10 +67,15 @@
AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId)
}
-class AppListRepositoryImpl(private val context: Context) : AppListRepository {
+class AppListRepositoryImpl(
+ private val context: Context,
+ private val featureFlags: FeatureFlags
+) : AppListRepository {
private val packageManager = context.packageManager
private val userManager = context.userManager
+ constructor(context: Context) : this(context, FeatureFlagsImpl())
+
override suspend fun loadApps(
userId: Int,
loadInstantApps: Boolean,
@@ -98,9 +105,13 @@
userId: Int,
matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> {
+ val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ val archivedPackagesFlag: Long = if (featureFlags.archiving())
+ PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
val regularFlags = ApplicationInfoFlags.of(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ disabledComponentsFlag or
+ archivedPackagesFlag
)
return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 517f67e..840bca8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -47,6 +47,8 @@
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
@@ -268,6 +270,40 @@
}
@Test
+ fun loadApps_archivedAppsEnabled() = runTest {
+ val fakeFlags = FakeFeatureFlagsImpl()
+ fakeFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ mockInstalledApplications(listOf(NORMAL_APP, ARCHIVED_APP), ADMIN_USER_ID)
+ val repository = AppListRepositoryImpl(context, fakeFlags)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP)
+ argumentCaptor<ApplicationInfoFlags> {
+ verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+ assertThat(firstValue.value).isEqualTo(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
+ PackageManager.MATCH_ARCHIVED_PACKAGES
+ )
+ }
+ }
+
+ @Test
+ fun loadApps_archivedAppsDisabled() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ argumentCaptor<ApplicationInfoFlags> {
+ verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+ assertThat(firstValue.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
+ }
+ }
+
+ @Test
fun showSystemPredicate_showSystem() = runTest {
val app = SYSTEM_APP
@@ -391,6 +427,12 @@
flags = ApplicationInfo.FLAG_SYSTEM
}
+ val ARCHIVED_APP = ApplicationInfo().apply {
+ packageName = "archived.app"
+ flags = ApplicationInfo.FLAG_SYSTEM
+ isArchived = true
+ }
+
fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
activityInfo = ActivityInfo().apply {
this.packageName = packageName
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index afdb92b..9d986f4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Kies profiel"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlik"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaat"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaaropsies"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktiveer ontwikkelaaropsies"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index ea8491b..b4887b9 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"መገለጫ ይምረጡ"</string>
<string name="category_personal" msgid="6236798763159385225">"የግል"</string>
<string name="category_work" msgid="4014193632325996115">"ሥራ"</string>
+ <string name="category_private" msgid="4244892185452788977">"የግል"</string>
<string name="category_clone" msgid="1554511758987195974">"አባዛ"</string>
<string name="development_settings_title" msgid="140296922921597393">"የገንቢዎች አማራጮች"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"የገንቢዎች አማራጮችን አንቃ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index c29e691..b8f92a3 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string>
<string name="category_personal" msgid="6236798763159385225">"التطبيقات الشخصية"</string>
<string name="category_work" msgid="4014193632325996115">"تطبيقات العمل"</string>
+ <string name="category_private" msgid="4244892185452788977">"ملف شخصي"</string>
<string name="category_clone" msgid="1554511758987195974">"استنساخ"</string>
<string name="development_settings_title" msgid="140296922921597393">"خيارات المطورين"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 8eff4f6..86b29c6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্ৰ’ফাইল বাছনি কৰক"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"কৰ্মস্থান-সম্পৰ্কীয়"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্ল’ন"</string>
<string name="development_settings_title" msgid="140296922921597393">"বিকাশকৰ্তাৰ বিকল্পসমূহ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"বিকাশকৰ্তা বিষয়ক বিকল্পসমূহ সক্ষম কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index dfd1b7b..6a08a25 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Şəxsi"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Şəxsi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonlayın"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer seçimləri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Developer variantlarını aktiv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 528e96e..ad829b9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirano"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 52614b7..7cd748c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Асабісты"</string>
<string name="category_work" msgid="4014193632325996115">"Працоўны"</string>
+ <string name="category_private" msgid="4244892185452788977">"Прыватныя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметры распрацоўшчыка"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index b28ae67..93fe481 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Избор на потребителски профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Служебни"</string>
+ <string name="category_private" msgid="4244892185452788977">"Частен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клониране"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опции за програмисти"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Активиране на опциите за програмисти"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index c91f14c..efdf304 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"অফিস"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্লোন"</string>
<string name="development_settings_title" msgid="140296922921597393">"ডেভেলপার বিকল্প"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index aa1073d..5d21dea 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirajte"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index ab6393a7..ea4a2fb 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Tria un perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Treball"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clona"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcions per a desenvolupadors"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activa les opcions per a desenvolupadors"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e7d5243..648e8dd 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vyberte profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobní"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovní"</string>
+ <string name="category_private" msgid="4244892185452788977">"Soukromé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pro vývojáře"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivovat možnosti pro vývojáře"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1612ac0..500bfc3 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vælg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Arbejde"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Indstillinger for udviklere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivér indstillinger for udviklere"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 73a44f1..df392f3 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -216,6 +216,8 @@
<string name="choose_profile" msgid="343803890897657450">"Profil auswählen"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Geschäftlich"</string>
+ <!-- no translation found for category_private (4244892185452788977) -->
+ <skip />
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Entwickleroptionen"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Entwickleroptionen aktivieren"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 7dfd679..a87c0d7 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string>
<string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string>
<string name="category_work" msgid="4014193632325996115">"Εργασίας"</string>
+ <string name="category_private" msgid="4244892185452788977">"Ιδιωτικό"</string>
<string name="category_clone" msgid="1554511758987195974">"Κλωνοποίηση"</string>
<string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 46dd47d..11a39b2 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a7c327f76..8a32195 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 291a68b..4276f59 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Elegir perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opciones para programador"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index b4bc319..8b7b211 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Seleccionar perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Habilitar opciones para desarrolladores"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index e5cbaf6..9c46b6c 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiili valimine"</string>
<string name="category_personal" msgid="6236798763159385225">"Isiklik"</string>
<string name="category_work" msgid="4014193632325996115">"Töö"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloonimine"</string>
<string name="development_settings_title" msgid="140296922921597393">"Arendaja valikud"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Arendaja valikute lubamine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 90d45eb..4436d52 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Aukeratu profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Pertsonalak"</string>
<string name="category_work" msgid="4014193632325996115">"Lanekoak"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribatua"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonatu"</string>
<string name="development_settings_title" msgid="140296922921597393">"Garatzaileentzako aukerak"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Gaitu garatzaileen aukerak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index f3f97c4..7ae6b7b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"انتخاب نمایه"</string>
<string name="category_personal" msgid="6236798763159385225">"شخصی"</string>
<string name="category_work" msgid="4014193632325996115">"کاری"</string>
+ <string name="category_private" msgid="4244892185452788977">"خصوصی"</string>
<string name="category_clone" msgid="1554511758987195974">"مشابهسازی"</string>
<string name="development_settings_title" msgid="140296922921597393">"گزینههای برنامهنویسان"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینههای برنامهنویس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 7f3d0f2..6822d08 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
<string name="category_personal" msgid="6236798763159385225">"Henkilökohtainen"</string>
<string name="category_work" msgid="4014193632325996115">"Työ"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yksityinen"</string>
<string name="category_clone" msgid="1554511758987195974">"Klooni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kehittäjäasetukset"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ota kehittäjäasetukset käyttöön"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index b7c3a81..12bc78e 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personnel"</string>
<string name="category_work" msgid="4014193632325996115">"Professionnel"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privés"</string>
<string name="category_clone" msgid="1554511758987195974">"Cloner"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a9e0c6a..527b473 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Perso"</string>
<string name="category_work" msgid="4014193632325996115">"Pro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 09c2969..00d7b6e 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escoller perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoal"</string>
<string name="category_work" msgid="4014193632325996115">"Traballo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 575c675..b0819fb 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"પ્રોફાઇલ પસંદ કરો"</string>
<string name="category_personal" msgid="6236798763159385225">"વ્યક્તિગત"</string>
<string name="category_work" msgid="4014193632325996115">"ઑફિસ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ખાનગી"</string>
<string name="category_clone" msgid="1554511758987195974">"ક્લોન કરો"</string>
<string name="development_settings_title" msgid="140296922921597393">"ડેવલપરના વિકલ્પો"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"વિકાસકર્તાનાં વિકલ્પો સક્ષમ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 655fac0..99572f3 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफ़ाइल चुनें"</string>
<string name="category_personal" msgid="6236798763159385225">"निजी"</string>
<string name="category_work" msgid="4014193632325996115">"वर्क"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेवलपर के लिए सेटिंग और टूल"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेवलपर के लिए सेटिंग और टूल चालू करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f0e16a4..735d3e9 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odabir profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobno"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloniranje"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za razvojne programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za razvojne programere"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 420e0e9..70b7d581 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil kiválasztása"</string>
<string name="category_personal" msgid="6236798763159385225">"Személyes"</string>
<string name="category_work" msgid="4014193632325996115">"Munkahelyi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privát"</string>
<string name="category_clone" msgid="1554511758987195974">"Klónozás"</string>
<string name="development_settings_title" msgid="140296922921597393">"Fejlesztői beállítások"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Fejlesztői beállítások engedélyezése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 705fcf6..9340a3b 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Ընտրեք պրոֆիլ"</string>
<string name="category_personal" msgid="6236798763159385225">"Անձնական"</string>
<string name="category_work" msgid="4014193632325996115">"Աշխատանքային"</string>
+ <string name="category_private" msgid="4244892185452788977">"Անձնական"</string>
<string name="category_clone" msgid="1554511758987195974">"Կլոն"</string>
<string name="development_settings_title" msgid="140296922921597393">"Մշակողի ընտրանքներ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Միացնել մշակողի ընտրանքները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index e33dd05..8cb3dca 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e1fb248..2f8ee7e 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Veldu snið"</string>
<string name="category_personal" msgid="6236798763159385225">"Persónulegt"</string>
<string name="category_work" msgid="4014193632325996115">"Vinna"</string>
+ <string name="category_private" msgid="4244892185452788977">"Lokað"</string>
<string name="category_clone" msgid="1554511758987195974">"Afrit"</string>
<string name="development_settings_title" msgid="140296922921597393">"Forritunarkostir"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Virkja valkosti þróunaraðila"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index b2cfd37..9fe9f30 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Scegli profilo"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Lavoro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privato"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opzioni sviluppatore"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Attiva Opzioni sviluppatore"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 9ca7477..56ce9649 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"בחירת פרופיל"</string>
<string name="category_personal" msgid="6236798763159385225">"אישי"</string>
<string name="category_work" msgid="4014193632325996115">"עבודה"</string>
+ <string name="category_private" msgid="4244892185452788977">"פרטי"</string>
<string name="category_clone" msgid="1554511758987195974">"שכפול"</string>
<string name="development_settings_title" msgid="140296922921597393">"אפשרויות למפתחים"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"הפעלת אפשרויות למפתחים"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index dcce2b4..50f57ef 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"プロファイルの選択"</string>
<string name="category_personal" msgid="6236798763159385225">"個人用"</string>
<string name="category_work" msgid="4014193632325996115">"仕事用"</string>
+ <string name="category_private" msgid="4244892185452788977">"非公開"</string>
<string name="category_clone" msgid="1554511758987195974">"クローン"</string>
<string name="development_settings_title" msgid="140296922921597393">"開発者向けオプション"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"開発者向けオプションの有効化"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index eb32e1c..46e406d 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"აირჩიეთ პროფილი"</string>
<string name="category_personal" msgid="6236798763159385225">"პირადი"</string>
<string name="category_work" msgid="4014193632325996115">"სამსახური"</string>
+ <string name="category_private" msgid="4244892185452788977">"პირადი"</string>
<string name="category_clone" msgid="1554511758987195974">"კლონის შექმნა"</string>
<string name="development_settings_title" msgid="140296922921597393">"პარამეტრები დეველოპერებისთვის"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"დეველოპერთა პარამეტრების ჩართვა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 73a8efd..42c8ca7 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профильді таңдау"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жұмыс"</string>
+ <string name="category_private" msgid="4244892185452788977">"Жеке"</string>
<string name="category_clone" msgid="1554511758987195974">"Клондау"</string>
<string name="development_settings_title" msgid="140296922921597393">"Әзірлеуші опциялары"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Әзірлеуші параметрлерін қосу"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 1c32e62..9503049 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ជ្រើសរើសកម្រងព័ត៌មាន"</string>
<string name="category_personal" msgid="6236798763159385225">"ផ្ទាល់ខ្លួន"</string>
<string name="category_work" msgid="4014193632325996115">"ការងារ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ឯកជន"</string>
<string name="category_clone" msgid="1554511758987195974">"ក្លូន"</string>
<string name="development_settings_title" msgid="140296922921597393">"ជម្រើសសម្រាប់អ្នកអភិវឌ្ឍន៍"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"បើកដំណើរការជម្រើសអ្នកអភិវឌ្ឍន៍"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ede347d..f9ec91f 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="category_personal" msgid="6236798763159385225">"ವೈಯಕ್ತಿಕ"</string>
<string name="category_work" msgid="4014193632325996115">"ಕೆಲಸ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ಖಾಸಗಿ"</string>
<string name="category_clone" msgid="1554511758987195974">"ಕ್ಲೋನ್"</string>
<string name="development_settings_title" msgid="140296922921597393">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳು"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 78bd616..a180880 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"프로필 선택"</string>
<string name="category_personal" msgid="6236798763159385225">"개인"</string>
<string name="category_work" msgid="4014193632325996115">"직장"</string>
+ <string name="category_private" msgid="4244892185452788977">"비공개"</string>
<string name="category_clone" msgid="1554511758987195974">"복사"</string>
<string name="development_settings_title" msgid="140296922921597393">"개발자 옵션"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"개발자 옵션 사용"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a0b9123..b47356b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профиль тандоо"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жумуш"</string>
+ <string name="category_private" msgid="4244892185452788977">"Купуя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Иштеп чыгуучунун параметрлери"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 96b2dc1..228eebe 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ເລືອກໂປຣໄຟລ໌"</string>
<string name="category_personal" msgid="6236798763159385225">"ສ່ວນໂຕ"</string>
<string name="category_work" msgid="4014193632325996115">"ບ່ອນເຮັດວຽກ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ສ່ວນຕົວ"</string>
<string name="category_clone" msgid="1554511758987195974">"ໂຄລນ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ຕົວເລືອກນັກພັດທະນາ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ເປີດໃຊ້ຕົວເລືອກນັກພັດທະນາ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 69630f4..5dfd2f4 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilio pasirinkimas"</string>
<string name="category_personal" msgid="6236798763159385225">"Asmeninės"</string>
<string name="category_work" msgid="4014193632325996115">"Darbo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatus"</string>
<string name="category_clone" msgid="1554511758987195974">"Identiška kopija"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kūrėjo parinktys"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Įgalinti kūrėjo parinktis"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 4f76cee..8b55355 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profila izvēlēšanās"</string>
<string name="category_personal" msgid="6236798763159385225">"Privāts"</string>
<string name="category_work" msgid="4014193632325996115">"Darba"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privāti"</string>
<string name="category_clone" msgid="1554511758987195974">"Klons"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izstrādātāju opcijas"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Izstrādātāju opciju iespējošana"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 1c80c65..72fea47 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изберете профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Работа"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Програмерски опции"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Овозможете ги програмерските опции"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 31e3c83..6308083f 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക"</string>
<string name="category_personal" msgid="6236798763159385225">"വ്യക്തിപരം"</string>
<string name="category_work" msgid="4014193632325996115">"ഔദ്യോഗികം"</string>
+ <string name="category_private" msgid="4244892185452788977">"സ്വകാര്യം"</string>
<string name="category_clone" msgid="1554511758987195974">"ക്ലോൺ ചെയ്യുക"</string>
<string name="development_settings_title" msgid="140296922921597393">"ഡെവലപ്പർ ഓപ്ഷനുകൾ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ഡെവലപ്പർ ഓപ്ഷനുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 5a636d9..8707f40 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профайл сонгох"</string>
<string name="category_personal" msgid="6236798763159385225">"Хувийн"</string>
<string name="category_work" msgid="4014193632325996115">"Ажил"</string>
+ <string name="category_private" msgid="4244892185452788977">"Хувийн"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Хөгжүүлэгчийн тохиргоо"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Хөгжүүлэгчийн сонголтыг идэвхжүүлэх"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 4d78e57..ab8b88c 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल निवडा"</string>
<string name="category_personal" msgid="6236798763159385225">"वैयक्तिक"</string>
<string name="category_work" msgid="4014193632325996115">"कार्य"</string>
+ <string name="category_private" msgid="4244892185452788977">"खाजगी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन करा"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेव्हलपर पर्याय"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेव्हलपर पर्याय सुरू करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 2ae69bd..2841c74 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Peribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Peribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dayakan pilihan pembangun"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 7d6f2a7..d915370 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ပရိုဖိုင်ကို ရွေးရန်"</string>
<string name="category_personal" msgid="6236798763159385225">"ကိုယ်ပိုင်"</string>
<string name="category_work" msgid="4014193632325996115">"အလုပ်"</string>
+ <string name="category_private" msgid="4244892185452788977">"သီးသန့်"</string>
<string name="category_clone" msgid="1554511758987195974">"ပုံတူပွားရန်"</string>
<string name="development_settings_title" msgid="140296922921597393">"ဆော့ဝဲလ်ရေးသူ ရွေးစရာများ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ဆော့ဖ်ဝဲရေးသူအတွက် ရွေးစရာများကို ဖွင့်ပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9e550fb..0a63c4b 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Velg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvikleralternativer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Slå på utvikleralternativer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1f6ad5b..206444d 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल रोज्नुहोस्"</string>
<string name="category_personal" msgid="6236798763159385225">"व्यक्तिगत"</string>
<string name="category_work" msgid="4014193632325996115">"काम"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"विकासकर्ताका विकल्पहरू"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"विकासकर्ता विकल्प सक्रिया गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 6a6f184..af4797c9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiel kiezen"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlijk"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaarsopties"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Opties voor ontwikkelaars aanzetten"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 980a374..bbdafc8 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ୍ ବାଛନ୍ତୁ"</string>
<string name="category_personal" msgid="6236798763159385225">"ବ୍ୟକ୍ତିଗତ"</string>
<string name="category_work" msgid="4014193632325996115">"ୱାର୍କ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ପ୍ରାଇଭେଟ"</string>
<string name="category_clone" msgid="1554511758987195974">"କ୍ଲୋନ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ଡେଭଲପରଙ୍କ ପାଇଁ ବିକଳ୍ପଗୁଡ଼ିକ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ଡେଭଲପର୍ ବିକଳ୍ପଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index dd3fa15..59eec15 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ"</string>
<string name="category_personal" msgid="6236798763159385225">"ਨਿੱਜੀ"</string>
<string name="category_work" msgid="4014193632325996115">"ਕੰਮ ਸੰਬੰਧੀ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ਨਿੱਜੀ"</string>
<string name="category_clone" msgid="1554511758987195974">"ਕਲੋਨ ਕਰੋ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ਵਿਕਾਸਕਾਰ ਚੋਣਾਂ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4ebd29..d5d37f0 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobiste"</string>
<string name="category_work" msgid="4014193632325996115">"Służbowe"</string>
+ <string name="category_private" msgid="4244892185452788977">"Prywatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcje programisty"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Włącz opcje programisty"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index bb6c7d8..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6bd5c2a..c817f88 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções de programador"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar as opções de programador"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index bb6c7d8..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f4399dc..5d855a5 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Alege un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Serviciu"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonează"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opțiuni pentru dezvoltatori"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activează opțiunile pentru dezvoltatori"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 5d45e29..9dac1d3 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбор профиля"</string>
<string name="category_personal" msgid="6236798763159385225">"Личный профиль"</string>
<string name="category_work" msgid="4014193632325996115">"Рабочий профиль"</string>
+ <string name="category_private" msgid="4244892185452788977">"Личный профиль"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Для разработчиков"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Включить параметры для разработчиков"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 996507d..4b36694 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"පැතිකඩ තෝරන්න"</string>
<string name="category_personal" msgid="6236798763159385225">"පෞද්ගලික"</string>
<string name="category_work" msgid="4014193632325996115">"කාර්යාලය"</string>
+ <string name="category_private" msgid="4244892185452788977">"පෞද්ගලික"</string>
<string name="category_clone" msgid="1554511758987195974">"ක්ලෝන කරන්න"</string>
<string name="development_settings_title" msgid="140296922921597393">"වර්ධක විකල්ප"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"සංවර්ධක විකල්ප සබල කිරීම"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a51acd4..30cc9fd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Výber profilu"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobné"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovné"</string>
+ <string name="category_private" msgid="4244892185452788977">"Súkromné"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonovanie"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pre vývojárov"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Povolenie možností vývojára"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 0d7188d..127c00d 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izbira profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osebno"</string>
<string name="category_work" msgid="4014193632325996115">"Delo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Zasebno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Možnosti za razvijalce"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogočanje možnosti za razvijalce"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c2bcce7..302e915 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Zgjidh profilin"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Punë"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Klono"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsionet e zhvilluesit"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivizo opsionet e zhvilluesit"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 09ff994..4d4ae0b 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изаберите профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лично"</string>
<string name="category_work" msgid="4014193632325996115">"Посао"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватно"</string>
<string name="category_clone" msgid="1554511758987195974">"Клонирано"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опције за програмере"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Омогући опције за програмере"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index fdd9d8c..8fc6af6 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvecklaralternativ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivera utvecklaralternativ"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index bd41752..f6cb654 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chagua wasifu"</string>
<string name="category_personal" msgid="6236798763159385225">"Binafsi"</string>
<string name="category_work" msgid="4014193632325996115">"Kazini"</string>
+ <string name="category_private" msgid="4244892185452788977">"Faragha"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Chaguo za wasanidi"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Washa chaguo za wasanidi programu"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index fd379f0..41788bf 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"சுயவிவரத்தைத் தேர்வு செய்க"</string>
<string name="category_personal" msgid="6236798763159385225">"தனிப்பட்டவை"</string>
<string name="category_work" msgid="4014193632325996115">"பணியிடம்"</string>
+ <string name="category_private" msgid="4244892185452788977">"தனிப்பட்டவை"</string>
<string name="category_clone" msgid="1554511758987195974">"குளோன்"</string>
<string name="development_settings_title" msgid="140296922921597393">"டெவெலப்பர் விருப்பங்கள்"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"டெவெலப்பர் விருப்பங்களை இயக்கு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 3e9d8be..4c71251 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ప్రొఫైల్ను ఎంచుకోండి"</string>
<string name="category_personal" msgid="6236798763159385225">"వ్యక్తిగతం"</string>
<string name="category_work" msgid="4014193632325996115">"వర్క్"</string>
+ <string name="category_private" msgid="4244892185452788977">"ప్రైవేట్"</string>
<string name="category_clone" msgid="1554511758987195974">"క్లోన్ చేయండి"</string>
<string name="development_settings_title" msgid="140296922921597393">"డెవలపర్ ఆప్షన్లు"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index e67f371..cb6291a 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"เลือกโปรไฟล์"</string>
<string name="category_personal" msgid="6236798763159385225">"ส่วนตัว"</string>
<string name="category_work" msgid="4014193632325996115">"งาน"</string>
+ <string name="category_private" msgid="4244892185452788977">"ส่วนตัว"</string>
<string name="category_clone" msgid="1554511758987195974">"โคลน"</string>
<string name="development_settings_title" msgid="140296922921597393">"ตัวเลือกสำหรับนักพัฒนาแอป"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอป"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 440bbe7..44d5de2 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pumili ng profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabaho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribado"</string>
<string name="category_clone" msgid="1554511758987195974">"I-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Mga opsyon ng developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"I-enable ang mga opsyon ng developer"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index b38012f..e33afa1 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -141,7 +141,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"İptal"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Eşleme işlemi, bağlantı kurulduğunda kişilerinize ve çağrı geçmişine erişim izni verir."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya şifre anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya geçiş anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile iletişim kurulamıyor."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Eşleme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tarafından reddedildi."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Bilgisayar"</string>
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Kişisel"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Gizli"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Geliştirici seçenekleri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Geliştirici seçeneklerini etkinleştir"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 4f08547..319caca 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Вибрати профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Особисте"</string>
<string name="category_work" msgid="4014193632325996115">"Робоче"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватні"</string>
<string name="category_clone" msgid="1554511758987195974">"Копія профілю"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметри розробника"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Увімкнути параметри розробника"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index ce67d15..10dacde 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"پروفائل منتخب کریں"</string>
<string name="category_personal" msgid="6236798763159385225">"ذاتی"</string>
<string name="category_work" msgid="4014193632325996115">"دفتر"</string>
+ <string name="category_private" msgid="4244892185452788977">"نجی"</string>
<string name="category_clone" msgid="1554511758987195974">"کلون کریں"</string>
<string name="development_settings_title" msgid="140296922921597393">"ڈویلپر کے اختیارات"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ڈویلپر کے اختیارات فعال کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 77da981..0a85c28 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilni tanlang"</string>
<string name="category_personal" msgid="6236798763159385225">"Shaxsiy"</string>
<string name="category_work" msgid="4014193632325996115">"Ish"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yopiq"</string>
<string name="category_clone" msgid="1554511758987195974">"Nusxalash"</string>
<string name="development_settings_title" msgid="140296922921597393">"Dasturchi sozlamalari"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dasturchi sozlamalarini yoqish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index bf510f6..3c34f4d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string>
<string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string>
<string name="category_work" msgid="4014193632325996115">"Công việc"</string>
+ <string name="category_private" msgid="4244892185452788977">"Riêng tư"</string>
<string name="category_clone" msgid="1554511758987195974">"Nhân bản"</string>
<string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 8e3145a..d385e67 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"选择个人资料"</string>
<string name="category_personal" msgid="6236798763159385225">"个人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私享"</string>
<string name="category_clone" msgid="1554511758987195974">"克隆"</string>
<string name="development_settings_title" msgid="140296922921597393">"开发者选项"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"启用开发者选项"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index aa9f21f..adcb4d8 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 3c65a4d..fddef2b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 08b04cc..82b7306 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Khetha iphrofayela"</string>
<string name="category_personal" msgid="6236798763159385225">"Okomuntu siqu"</string>
<string name="category_work" msgid="4014193632325996115">"Umsebenzi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Okuyimfihlo"</string>
<string name="category_clone" msgid="1554511758987195974">"Yenza i-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izinketho Zonjiniyela"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Nika amandla izinketho zonjiniyela"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9687674..6eaabbb 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/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index 363e20aa..7fbd35b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import android.widget.Switch;
+import android.widget.CompoundButton;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
@@ -35,7 +35,7 @@
*/
public class PrimarySwitchPreference extends RestrictedPreference {
- private Switch mSwitch;
+ private CompoundButton mSwitch;
private boolean mChecked;
private boolean mCheckedSet;
private boolean mEnableSwitch = true;
@@ -65,7 +65,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- mSwitch = (Switch) holder.findViewById(R.id.switchWidget);
+ mSwitch = (CompoundButton) holder.findViewById(R.id.switchWidget);
if (mSwitch != null) {
mSwitch.setOnClickListener(v -> {
if (mSwitch != null && !mSwitch.isEnabled()) {
@@ -153,7 +153,7 @@
setSwitchEnabled(admin == null);
}
- public Switch getSwitch() {
+ public CompoundButton getSwitch() {
return mSwitch;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 758f090..60321eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -36,18 +36,17 @@
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
-import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import androidx.preference.SwitchPreferenceCompat;
import com.android.settingslib.utils.BuildCompatUtils;
/**
- * Version of SwitchPreference that can be disabled by a device admin
+ * Version of SwitchPreferenceCompat that can be disabled by a device admin
* using a user restriction.
*/
-public class RestrictedSwitchPreference extends SwitchPreference {
+public class RestrictedSwitchPreference extends SwitchPreferenceCompat {
RestrictedPreferenceHelper mHelper;
AppOpsManager mAppOpsManager;
boolean mUseAdditionalSummary = false;
@@ -93,8 +92,7 @@
}
public RestrictedSwitchPreference(Context context, AttributeSet attrs) {
- this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
- android.R.attr.switchPreferenceStyle));
+ this(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle);
}
public RestrictedSwitchPreference(Context context) {
@@ -113,7 +111,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final View switchView = holder.findViewById(android.R.id.switch_widget);
+ final View switchView = holder.findViewById(androidx.preference.R.id.switchWidget);
if (switchView != null) {
final View rootView = switchView.getRootView();
rootView.setFilterTouchesWhenObscured(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 660090d..1900575 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 @@
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.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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 57867be..f83e37b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -19,6 +19,9 @@
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -28,10 +31,11 @@
import android.os.Build;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+
import java.util.ArrayList;
import java.util.List;
-
-import androidx.annotation.RequiresApi;
+import java.util.concurrent.Executor;
/**
* VolumeControlProfile handles Bluetooth Volume Control Controller role
@@ -102,6 +106,88 @@
BluetoothProfile.VOLUME_CONTROL);
}
+
+ /**
+ * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
+ * operation of this profile.
+ *
+ * Repeated registration of the same <var>callback</var> object will have no effect after
+ * the first call to this method, even when the <var>executor</var> is different. API caller
+ * would have to call {@link #unregisterCallback(BluetoothVolumeControl.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 BluetoothVolumeControl.Callback}
+ * @throws IllegalArgumentException if a null executor or callback is given
+ */
+ public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothVolumeControl.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 BluetoothVolumeControl.Callback}.
+ * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
+ * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+ *
+ * <p>Callbacks are automatically unregistered when application process goes away
+ *
+ * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
+ * @throws IllegalArgumentException when callback is null or when no callback is registered
+ */
+ public void unregisterCallback(@NonNull BluetoothVolumeControl.Callback callback) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot unregister callback.");
+ return;
+ }
+ mService.unregisterCallback(callback);
+ }
+
+ /**
+ * Tells the remote device to set a volume offset to the absolute volume.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @param volumeOffset volume offset to be set on the remote device
+ */
+ public void setVolumeOffset(BluetoothDevice device,
+ @IntRange(from = -255, to = 255) int volumeOffset) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+ return;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot set volume offset.");
+ return;
+ }
+ mService.setVolumeOffset(device, volumeOffset);
+ }
+
+ /**
+ * Provides information about the possibility to set volume offset on the remote device.
+ * If the remote device supports Volume Offset Control Service, it is automatically
+ * connected.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @return {@code true} if volume offset function is supported and available to use on the
+ * remote device. When Bluetooth is off, the return value should always be
+ * {@code false}.
+ */
+ public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot get is volume offset available.");
+ return false;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot get is volume offset available.");
+ return false;
+ }
+ return mService.isVolumeOffsetAvailable(device);
+ }
+
@Override
public boolean accessProfileEnabled() {
return false;
@@ -113,12 +199,12 @@
}
/**
- * Get VolumeControlProfile devices matching connection states{
+ * Gets VolumeControlProfile devices matching connection states{
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
*
* @return Matching device list
- * @code BluetoothProfile.STATE_CONNECTED,
- * @code BluetoothProfile.STATE_CONNECTING,
- * @code BluetoothProfile.STATE_DISCONNECTING}
*/
public List<BluetoothDevice> getConnectedDevices() {
if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
index a5f69ff..f988837 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
@@ -18,7 +18,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.preference.PreferenceGroupAdapter
-import androidx.preference.SwitchPreference
+import androidx.preference.TwoStatePreference
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.jank.InteractionJankMonitor
import java.util.concurrent.Executors
@@ -37,13 +37,16 @@
const val MONITORED_ANIMATION_DURATION_MS = 300L
/**
- * Detects the jank when click on a SwitchPreference.
+ * Detects the jank when click on a TwoStatePreference.
*
* @param recyclerView the recyclerView contains the preference
* @param preference the clicked preference
*/
@JvmStatic
- fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) {
+ fun detectSwitchPreferenceClickJank(
+ recyclerView: RecyclerView,
+ preference: TwoStatePreference,
+ ) {
val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return
val adapterPosition = adapter.getPreferenceAdapterPosition(preference)
val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index c9540c7..8bdbee3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -26,7 +26,6 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.PowerManager;
-import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -202,10 +201,10 @@
}
private static void setBatterySaverConfirmationAcknowledged(Context context) {
- Secure.putIntForUser(context.getContentResolver(),
- Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1, UserHandle.USER_CURRENT);
- Secure.putIntForUser(context.getContentResolver(),
- Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1, UserHandle.USER_CURRENT);
+ Secure.putInt(context.getContentResolver(),
+ Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
+ Secure.putInt(context.getContentResolver(),
+ Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 2999c83..1501e27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -31,8 +31,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import android.widget.CompoundButton;
import android.widget.ImageView;
-import android.widget.Switch;
import android.widget.Toast;
import androidx.preference.Preference;
@@ -138,7 +138,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final Switch switchWidget = getSwitch();
+ final CompoundButton switchWidget = getSwitch();
if (switchWidget != null) {
// Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
switchWidget.setOnClickListener(v -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 70956e9..9ab3b47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@@ -54,8 +55,11 @@
private final RouteCallback mRouteCallback = new RouteCallback();
private final TransferCallback mTransferCallback = new TransferCallback();
private final ControllerCallback mControllerCallback = new ControllerCallback();
- private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
- new RouteListingPreferenceCallback();
+ private final Consumer<RouteListingPreference> mRouteListingPreferenceCallback =
+ (preference) -> {
+ notifyRouteListingPreferenceUpdated(preference);
+ refreshDevices();
+ };
// TODO: b/192657812 - Create factory method in InfoMediaManager to return
// RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
@@ -83,7 +87,8 @@
@Override
protected void startScanOnRouter() {
mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
- mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerRouteListingPreferenceUpdatedCallback(
+ mExecutor, mRouteListingPreferenceCallback);
mRouter.registerTransferCallback(mExecutor, mTransferCallback);
mRouter.registerControllerCallback(mExecutor, mControllerCallback);
mRouter.startScan();
@@ -94,7 +99,7 @@
mRouter.stopScan();
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
- mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+ mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
mRouter.unregisterRouteCallback(mRouteCallback);
}
@@ -308,13 +313,4 @@
refreshDevices();
}
}
-
- private final class RouteListingPreferenceCallback
- extends MediaRouter2.RouteListingPreferenceCallback {
- @Override
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
- notifyRouteListingPreferenceUpdated(preference);
- refreshDevices();
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 673f243..2272654 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 @@
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 @@
}
}
- 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 @@
return time - remainder + multiple;
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
index d9cf9f2..debfa49 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
@@ -23,8 +23,8 @@
import android.content.Context;
import android.view.LayoutInflater;
+import android.widget.CompoundButton;
import android.widget.LinearLayout;
-import android.widget.Switch;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceViewHolder;
@@ -62,7 +62,7 @@
@Test
public void setChecked_shouldUpdateButtonCheckedState() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
mPreference.onBindViewHolder(mHolder);
mPreference.setChecked(true);
@@ -74,7 +74,7 @@
@Test
public void setSwitchEnabled_shouldUpdateButtonEnabledState() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
mPreference.onBindViewHolder(mHolder);
mPreference.setSwitchEnabled(true);
@@ -86,7 +86,7 @@
@Test
public void setSwitchEnabled_shouldUpdateButtonEnabledState_beforeViewBound() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
mPreference.setSwitchEnabled(false);
mPreference.onBindViewHolder(mHolder);
@@ -97,7 +97,7 @@
public void clickWidgetView_shouldToggleButton() {
assertThat(mWidgetView).isNotNull();
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
mPreference.onBindViewHolder(mHolder);
toggle.performClick();
@@ -111,7 +111,7 @@
public void clickWidgetView_shouldNotToggleButtonIfDisabled() {
assertThat(mWidgetView).isNotNull();
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
mPreference.onBindViewHolder(mHolder);
toggle.setEnabled(false);
@@ -122,7 +122,7 @@
@Test
public void clickWidgetView_shouldNotifyPreferenceChanged() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
mPreference.setOnPreferenceChangeListener(listener);
@@ -139,7 +139,7 @@
@Test
public void setDisabledByAdmin_hasEnforcedAdmin_shouldDisableButton() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
toggle.setEnabled(true);
mPreference.onBindViewHolder(mHolder);
@@ -149,7 +149,7 @@
@Test
public void setDisabledByAdmin_noEnforcedAdmin_shouldEnableButton() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
toggle.setEnabled(false);
mPreference.onBindViewHolder(mHolder);
@@ -159,7 +159,7 @@
@Test
public void onBindViewHolder_toggleButtonShouldHaveContentDescription() {
- final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget);
+ final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget);
final String label = "TestButton";
mPreference.setTitle(label);
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 03a792a..7e3263b 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 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 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 @@
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 @@
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/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
new file mode 100644
index 0000000..c560627
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.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 com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+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 VolumeControlProfileTest {
+
+ private static final int TEST_VOLUME_OFFSET = 10;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
+ @Mock
+ private BluetoothVolumeControl mService;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private BluetoothProfile.ServiceListener mServiceListener;
+ private VolumeControlProfile mProfile;
+
+ @Before
+ public void setUp() {
+ mProfile = new VolumeControlProfile(mContext, mDeviceManager, mProfileManager);
+ final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+ final ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(bluetoothManager.getAdapter());
+ mServiceListener = shadowBluetoothAdapter.getServiceListener();
+ }
+
+ @Test
+ public void onServiceConnected_isProfileReady() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ assertThat(mProfile.isProfileReady()).isTrue();
+ verify(mProfileManager).callServiceConnectedListeners();
+ }
+
+ @Test
+ public void onServiceDisconnected_isProfileNotReady() {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.VOLUME_CONTROL);
+
+ assertThat(mProfile.isProfileReady()).isFalse();
+ verify(mProfileManager).callServiceDisconnectedListeners();
+ }
+
+ @Test
+ public void getConnectionStatus_returnCorrectConnectionState() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionState(mBluetoothDevice))
+ .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+ assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyForbidden_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+ .isEqualTo(CONNECTION_POLICY_ALLOWED);
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void getConnectedDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ int[] connectedStates = new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectedList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectedStates))
+ .thenReturn(connectedList);
+
+ assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+ }
+
+ @Test
+ public void registerCallback_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ final Executor executor = (command -> new Thread(command).start());
+ final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ mProfile.registerCallback(executor, callback);
+
+ verify(mService).registerCallback(executor, callback);
+ }
+
+ @Test
+ public void unregisterCallback_verifyIsCalled() {
+ final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.unregisterCallback(callback);
+
+ verify(mService).unregisterCallback(callback);
+ }
+
+ @Test
+ public void setVolumeOffset_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+
+ verify(mService).setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+ }
+
+ @Test
+ public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
+
+ final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+ verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+ assertThat(available).isTrue();
+ }
+
+ @Test
+ public void isVolumeOffsetAvailable_verifyIsCalledAndReturnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(false);
+
+ final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+ verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+ assertThat(available).isFalse();
+ }
+}
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 ae54206..2e7905f 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 @@
}
@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/Android.bp b/packages/SettingsProvider/Android.bp
index f4ca260..a4a9290 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -17,9 +17,10 @@
],
}
-android_app {
- name: "SettingsProvider",
+android_library {
+ name: "SettingsProviderLib",
defaults: ["platform_app_defaults"],
+ manifest: "AndroidManifestLib.xml",
resource_dirs: ["res"],
srcs: [
"src/**/*.java",
@@ -32,38 +33,37 @@
],
static_libs: [
"device_config_service_flags_java",
- "junit",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
],
platform_apis: true,
+}
+
+android_app {
+ name: "SettingsProvider",
+ defaults: ["platform_app_defaults"],
+ resource_dirs: [],
+ static_libs: ["SettingsProviderLib"],
+ platform_apis: true,
certificate: "platform",
privileged: true,
}
android_test {
name: "SettingsProviderTest",
- // Note we statically link several classes to do some unit tests. It's not accessible otherwise
- // because this test is not an instrumentation test. (because the target runs in the system process.)
srcs: [
"test/**/*.java",
- "src/android/provider/settings/backup/*",
- "src/android/provider/settings/validators/*",
- "src/com/android/providers/settings/GenerationRegistry.java",
- "src/com/android/providers/settings/SettingsBackupAgent.java",
- "src/com/android/providers/settings/SettingsState.java",
- "src/com/android/providers/settings/SettingsHelper.java",
- "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java",
],
static_libs: [
+ // Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
+ // because this test is not an instrumentation test. (because the target runs in the system process.)
+ "SettingsProviderLib",
+
"androidx.test.rules",
- "device_config_service_flags_java",
"flag-junit",
+ "junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
- "SettingsLibDeviceStateRotationLock",
- "SettingsLibDisplayUtils",
- "platform-test-annotations",
"truth",
],
libs: [
@@ -71,12 +71,7 @@
"android.test.mock",
"unsupportedappusage",
],
- resource_dirs: ["res"],
- aaptflags: [
- "--auto-add-overlay",
- "--extra-packages",
- "com.android.providers.settings",
- ],
+ resource_dirs: [],
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
diff --git a/packages/SettingsProvider/AndroidManifestLib.xml b/packages/SettingsProvider/AndroidManifestLib.xml
new file mode 100644
index 0000000..a20b539
--- /dev/null
+++ b/packages/SettingsProvider/AndroidManifestLib.xml
@@ -0,0 +1,2 @@
+<manifest package="com.android.providers.settings">
+</manifest>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c6e9c03..603e19f 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 @@
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 0727677..5457c35 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 @@
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/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 02ec486..cd35f67 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -45,7 +45,8 @@
private static final boolean DEBUG = false;
- private final Object mLock;
+ // This lock is not the same lock used in SettingsProvider and SettingsState
+ private final Object mLock = new Object();
// Key -> backingStore mapping
@GuardedBy("mLock")
@@ -74,8 +75,7 @@
private final int mMaxNumBackingStore;
- GenerationRegistry(Object lock, int maxNumUsers) {
- mLock = lock;
+ GenerationRegistry(int maxNumUsers) {
// Add some buffer to maxNumUsers to accommodate corner cases when the actual number of
// users in the system exceeds the limit
maxNumUsers = maxNumUsers + 2;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f06b31c..7db189b 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 @@
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 4e2fad0..5acc1ca 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.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 @@
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 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 @@
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 @@
}
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 @@
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 @@
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 @@
return getAllSystemSettings(userId, projection);
}
}
-
- default: {
+ default -> {
throw new IllegalArgumentException("Invalid Uri path:" + uri);
}
}
@@ -708,30 +671,27 @@
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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
}
// 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 @@
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 @@
}
// 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 @@
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 @@
}
// 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 @@
// 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 @@
}
// 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 @@
}
// 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 @@
}
// 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 @@
// 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 @@
* 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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION");
}
- break;
+ }
}
if (!ai.isInstantApp()) {
return;
@@ -2306,23 +2247,22 @@
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 @@
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 @@
}
}
- private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) {
+ private static int resolveCallingUserIdEnforcingPermissions(int requestingUserId) {
if (requestingUserId == UserHandle.getCallingUserId()) {
return requestingUserId;
}
@@ -2654,28 +2594,28 @@
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 @@
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 @@
}
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 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 @@
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(
@@ -2956,10 +2884,11 @@
public SettingsRegistry() {
mHandler = new MyHandler(getContext().getMainLooper());
- mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers());
+ mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers());
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 @@
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 @@
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 @@
}
}
+ @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 @@
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 @@
// 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 @@
}
}
+ @GuardedBy("mLock")
public void removeUserStateLocked(int userId, boolean permanently) {
// We always keep the global settings in memory.
@@ -3166,12 +3102,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ @GuardedBy("mLock")
public void onUidRemovedLocked(int uid) {
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
UserHandle.getUserId(uid));
@@ -3490,19 +3418,7 @@
}
}
- @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 @@
}
}
+ @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 @@
migrateLegacySettingsForUserLocked(dbHelper, database, userId);
}
+ @GuardedBy("mLock")
private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
SQLiteDatabase database, int userId) {
// Move over the system settings.
@@ -3596,6 +3514,7 @@
}
}
+ @GuardedBy("mLock")
private void migrateLegacySettingsLocked(SettingsState settingsState,
SQLiteDatabase database, String table) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
@@ -3630,7 +3549,7 @@
}
}
- @GuardedBy("secureSettings.mLock")
+ @GuardedBy("mLock")
private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);
@@ -3706,6 +3625,7 @@
name, type, changeType);
}
+ @GuardedBy("mLock")
private void notifyForConfigSettingsChangeLocked(int key, String prefix,
List<String> changedSettings) {
@@ -3787,30 +3707,18 @@
}
}
- 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
}
}
+ @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 7bca944..9dacade 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -904,6 +904,7 @@
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/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 12865f4..8029785 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -36,7 +36,7 @@
public class GenerationRegistryTest {
@Test
public void testGenerationsWithRegularSetting() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(2);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -93,7 +93,7 @@
@Test
public void testGenerationsWithConfigSetting() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(1);
final String prefix = "test_namespace/";
final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
@@ -110,7 +110,7 @@
@Test
public void testMaxNumBackingStores() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(2);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) {
@@ -133,7 +133,7 @@
@Test
public void testMaxSizeBackingStore() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(1);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -153,7 +153,7 @@
@Test
public void testUnsetSettings() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(1);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -172,7 +172,7 @@
@Test
public void testGlobalSettings() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
+ final GenerationRegistry generationRegistry = new GenerationRegistry(2);
final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
final String testGlobalSetting = "test_global_setting";
final Bundle b = new Bundle();
@@ -190,11 +190,11 @@
@Test
public void testNumberOfBackingStores() {
- GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0);
+ GenerationRegistry generationRegistry = new GenerationRegistry(0);
// Test that the capacity of the backing stores is always valid
assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
GenerationRegistry.MIN_NUM_BACKING_STORE);
- generationRegistry = new GenerationRegistry(new Object(), 100);
+ generationRegistry = new GenerationRegistry(100);
// Test that the capacity of the backing stores is always valid
assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
GenerationRegistry.MAX_NUM_BACKING_STORE);
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 2b33057..2984cd3 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.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 @@
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 @@
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 @@
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 11ae9c3..10d04d3 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/SoundPicker/res/values-af/strings.xml b/packages/SoundPicker/res/values-af/strings.xml
index 7396b76..fd857b1 100644
--- a/packages/SoundPicker/res/values-af/strings.xml
+++ b/packages/SoundPicker/res/values-af/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan nie gepasmaakte luitoon byvoeg nie"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan nie gepasmaakte luitoon uitvee nie"</string>
<string name="app_label" msgid="3091611356093417332">"Klanke"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die lys is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Klank"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index bd1c24b..85206c0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ብጁ የጥሪ ቅላጼን ማከል አልተቻለም"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ብጁ የጥሪ ቅላጼን መሰረዝ አልተቻለም"</string>
<string name="app_label" msgid="3091611356093417332">"ድምፆች"</string>
- <string name="empty_list" msgid="2871978423955821191">"ዝርዝሩ ባዶ ነው"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ድምፅ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ንዝረት"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index 805c7cf..f8844e9 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
<string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
- <string name="empty_list" msgid="2871978423955821191">"القائمة فارغة."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"الصوت"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"الاهتزاز"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-as/strings.xml b/packages/SoundPicker/res/values-as/strings.xml
index 0a1cd1b..5d6bc5d 100644
--- a/packages/SoundPicker/res/values-as/strings.xml
+++ b/packages/SoundPicker/res/values-as/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন যোগ কৰিব পৰা নগ\'ল"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন মচিব পৰা নগ\'ল"</string>
<string name="app_label" msgid="3091611356093417332">"ধ্বনিসমূহ"</string>
- <string name="empty_list" msgid="2871978423955821191">"সূচীখন খালী আছে"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ধ্বনি"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"কম্পন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-az/strings.xml b/packages/SoundPicker/res/values-az/strings.xml
index a308329..e32c3eb 100644
--- a/packages/SoundPicker/res/values-az/strings.xml
+++ b/packages/SoundPicker/res/values-az/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Fərdi zəng səsi əlavə etmək mümkün deyil"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Fərdi zəng səsini silmək mümkün deyil"</string>
<string name="app_label" msgid="3091611356093417332">"Səslər"</string>
- <string name="empty_list" msgid="2871978423955821191">"Siyahı boşdur"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Səs"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasiya"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
index 2a7a196..947c85c 100644
--- a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije uspelo"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije uspelo"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-be/strings.xml b/packages/SoundPicker/res/values-be/strings.xml
index 431a301..6f7fc68 100644
--- a/packages/SoundPicker/res/values-be/strings.xml
+++ b/packages/SoundPicker/res/values-be/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Немагчыма дадаць карыстальніцкі рынгтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Немагчыма выдаліць карыстальніцкі рынгтон"</string>
<string name="app_label" msgid="3091611356093417332">"Гукі"</string>
- <string name="empty_list" msgid="2871978423955821191">"Спіс пусты"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Гук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрацыя"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bg/strings.xml b/packages/SoundPicker/res/values-bg/strings.xml
index 7447af6..4277d28 100644
--- a/packages/SoundPicker/res/values-bg/strings.xml
+++ b/packages/SoundPicker/res/values-bg/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Персонализираната мелодия не може да се добави"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Персонализираната мелодия не може да се изтрие"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списъкът е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибриране"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bn/strings.xml b/packages/SoundPicker/res/values-bn/strings.xml
index c31b36a..276594a 100644
--- a/packages/SoundPicker/res/values-bn/strings.xml
+++ b/packages/SoundPicker/res/values-bn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"কাস্টম রিংটোন যোগ করা গেল না"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"কাস্টম রিংটোন মোছা গেল না"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"তালিকায় কিছু নেই"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"সাউন্ড"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ভাইব্রেশন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bs/strings.xml b/packages/SoundPicker/res/values-bs/strings.xml
index e65b90d..0c8d33f 100644
--- a/packages/SoundPicker/res/values-bs/strings.xml
+++ b/packages/SoundPicker/res/values-bs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nije moguće dodati prilagođenu melodiju zvona"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nije moguće izbrisati prilagođenu melodiju zvona"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ca/strings.xml b/packages/SoundPicker/res/values-ca/strings.xml
index 33839bc..ed96f70 100644
--- a/packages/SoundPicker/res/values-ca/strings.xml
+++ b/packages/SoundPicker/res/values-ca/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No es pot afegir el so de trucada personalitzat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No es pot suprimir el so de trucada personalitzat"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La llista és buida"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"So"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibració"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-cs/strings.xml b/packages/SoundPicker/res/values-cs/strings.xml
index 612a6b2..dc67c96 100644
--- a/packages/SoundPicker/res/values-cs/strings.xml
+++ b/packages/SoundPicker/res/values-cs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Vlastní vyzvánění se nepodařilo přidat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Vlastní vyzvánění se nepodařilo smazat"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prázdný"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrace"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-da/strings.xml b/packages/SoundPicker/res/values-da/strings.xml
index 56c4d23..b4437dc 100644
--- a/packages/SoundPicker/res/values-da/strings.xml
+++ b/packages/SoundPicker/res/values-da/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Den tilpassede ringetone kunne ikke tilføjes"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Den tilpassede ringetone kunne ikke slettes"</string>
<string name="app_label" msgid="3091611356093417332">"Lyde"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-de/strings.xml b/packages/SoundPicker/res/values-de/strings.xml
index b004e2a..8be3aaa 100644
--- a/packages/SoundPicker/res/values-de/strings.xml
+++ b/packages/SoundPicker/res/values-de/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Benutzerdefinierter Klingelton konnte nicht hinzugefügt werden"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Benutzerdefinierter Klingelton konnte nicht gelöscht werden"</string>
<string name="app_label" msgid="3091611356093417332">"Töne"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die Liste ist leer"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ton"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-el/strings.xml b/packages/SoundPicker/res/values-el/strings.xml
index bbcb1f5..41e9b0c 100644
--- a/packages/SoundPicker/res/values-el/strings.xml
+++ b/packages/SoundPicker/res/values-el/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Δεν είναι δυνατή η προσθήκη προσαρμοσμένου ήχου κλήσης"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Δεν είναι δυνατή η διαγραφή προσαρμοσμένου ήχου κλήσης"</string>
<string name="app_label" msgid="3091611356093417332">"Ήχοι"</string>
- <string name="empty_list" msgid="2871978423955821191">"Η λίστα είναι κενή"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ήχος"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Δόνηση"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rAU/strings.xml b/packages/SoundPicker/res/values-en-rAU/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rAU/strings.xml
+++ b/packages/SoundPicker/res/values-en-rAU/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index d887082..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rGB/strings.xml b/packages/SoundPicker/res/values-en-rGB/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rGB/strings.xml
+++ b/packages/SoundPicker/res/values-en-rGB/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rIN/strings.xml b/packages/SoundPicker/res/values-en-rIN/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rIN/strings.xml
+++ b/packages/SoundPicker/res/values-en-rIN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rXC/strings.xml b/packages/SoundPicker/res/values-en-rXC/strings.xml
index d26c89b..8397e0b 100644
--- a/packages/SoundPicker/res/values-en-rXC/strings.xml
+++ b/packages/SoundPicker/res/values-en-rXC/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es-rUS/strings.xml b/packages/SoundPicker/res/values-es-rUS/strings.xml
index 211bfed..5bf73b2 100644
--- a/packages/SoundPicker/res/values-es-rUS/strings.xml
+++ b/packages/SoundPicker/res/values-es-rUS/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se puede agregar el tono personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se puede borrar el tono personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es/strings.xml b/packages/SoundPicker/res/values-es/strings.xml
index 3fdad74..a77f656 100644
--- a/packages/SoundPicker/res/values-es/strings.xml
+++ b/packages/SoundPicker/res/values-es/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se ha podido añadir un tono de llamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se ha podido eliminar un tono de llamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-et/strings.xml b/packages/SoundPicker/res/values-et/strings.xml
index 3d9ee94..fa680ac 100644
--- a/packages/SoundPicker/res/values-et/strings.xml
+++ b/packages/SoundPicker/res/values-et/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kohandatud helinat ei õnnestu lisada"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kohandatud helinat ei õnnestu kustutada"</string>
<string name="app_label" msgid="3091611356093417332">"Helid"</string>
- <string name="empty_list" msgid="2871978423955821191">"See loend on tühi"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Heli"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibreerimine"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-eu/strings.xml b/packages/SoundPicker/res/values-eu/strings.xml
index 1f929bf..e8e07fe 100644
--- a/packages/SoundPicker/res/values-eu/strings.xml
+++ b/packages/SoundPicker/res/values-eu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ezin da gehitu tonu pertsonalizatua"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ezin da ezabatu tonu pertsonalizatua"</string>
<string name="app_label" msgid="3091611356093417332">"Soinuak"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zerrenda hutsik dago"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Soinua"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Dardara"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fa/strings.xml b/packages/SoundPicker/res/values-fa/strings.xml
index 3b75ac9..769d5d5 100644
--- a/packages/SoundPicker/res/values-fa/strings.xml
+++ b/packages/SoundPicker/res/values-fa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"افزودن آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حذف آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="app_label" msgid="3091611356093417332">"صداها"</string>
- <string name="empty_list" msgid="2871978423955821191">"فهرست خالی است"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"صدا"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"لرزش"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fi/strings.xml b/packages/SoundPicker/res/values-fi/strings.xml
index 15bf658..fcda098 100644
--- a/packages/SoundPicker/res/values-fi/strings.xml
+++ b/packages/SoundPicker/res/values-fi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Muokatun soittoäänen lisääminen epäonnistui."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Muokatun soittoäänen poistaminen epäonnistui."</string>
<string name="app_label" msgid="3091611356093417332">"Äänet"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista on tyhjä"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ääni"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Värinä"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr-rCA/strings.xml b/packages/SoundPicker/res/values-fr-rCA/strings.xml
index 1cc170f..4d4545f 100644
--- a/packages/SoundPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-fr-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr/strings.xml b/packages/SoundPicker/res/values-fr/strings.xml
index ade2c16..9452e70 100644
--- a/packages/SoundPicker/res/values-fr/strings.xml
+++ b/packages/SoundPicker/res/values-fr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gl/strings.xml b/packages/SoundPicker/res/values-gl/strings.xml
index 83f2b2e..59a9d06 100644
--- a/packages/SoundPicker/res/values-gl/strings.xml
+++ b/packages/SoundPicker/res/values-gl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Non se pode engadir un ton de chamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Non se pode eliminar un ton de chamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está baleira"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gu/strings.xml b/packages/SoundPicker/res/values-gu/strings.xml
index 8207512..209769f 100644
--- a/packages/SoundPicker/res/values-gu/strings.xml
+++ b/packages/SoundPicker/res/values-gu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"કસ્ટમ રિંગટોન ઉમેરવામાં અસમર્થ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"કસ્ટમ રિંગટોન કાઢી નાખવામાં અસમર્થ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"સૂચિ ખાલી છે"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"સાઉન્ડ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"વાઇબ્રેશન"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hi/strings.xml b/packages/SoundPicker/res/values-hi/strings.xml
index 304201f..ab3b7f8 100644
--- a/packages/SoundPicker/res/values-hi/strings.xml
+++ b/packages/SoundPicker/res/values-hi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आपके मुताबिक रिंगटोन नहीं जोड़ी जा सकी"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आपके मुताबिक रिंगटोन नहीं हटाई जा सकी"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"यह सूची खाली है"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"साउंड"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"वाइब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hr/strings.xml b/packages/SoundPicker/res/values-hr/strings.xml
index 642c7d5..3adc500 100644
--- a/packages/SoundPicker/res/values-hr/strings.xml
+++ b/packages/SoundPicker/res/values-hr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije moguće"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije moguće"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Popis je prazan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hu/strings.xml b/packages/SoundPicker/res/values-hu/strings.xml
index 401da84..32d4ba9 100644
--- a/packages/SoundPicker/res/values-hu/strings.xml
+++ b/packages/SoundPicker/res/values-hu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nem sikerült hozzáadni az egyéni csengőhangot"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nem sikerült törölni az egyéni csengőhangot"</string>
<string name="app_label" msgid="3091611356093417332">"Hangok"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista üres"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hang"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rezgés"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hy/strings.xml b/packages/SoundPicker/res/values-hy/strings.xml
index d57345c..da8934f 100644
--- a/packages/SoundPicker/res/values-hy/strings.xml
+++ b/packages/SoundPicker/res/values-hy/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Հնարավոր չէ հատուկ զանգերանգ ավելացնել"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Հնարավոր չէ ջնջել հատուկ զանգերանգը"</string>
<string name="app_label" msgid="3091611356093417332">"Ձայներ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Ցանկը դատարկ է"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ձայն"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Թրթռոց"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 518a09d..86dce64 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Daftar ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suara"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-is/strings.xml b/packages/SoundPicker/res/values-is/strings.xml
index 7f05c79..d0fce78 100644
--- a/packages/SoundPicker/res/values-is/strings.xml
+++ b/packages/SoundPicker/res/values-is/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Get ekki bætt sérsniðnum hringitóni við"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Get ekki eytt sérsniðnum hringitóni"</string>
<string name="app_label" msgid="3091611356093417332">"Hljóð"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listinn er auður"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hljóð"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titringur"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-it/strings.xml b/packages/SoundPicker/res/values-it/strings.xml
index e1d8e19..632cb41 100644
--- a/packages/SoundPicker/res/values-it/strings.xml
+++ b/packages/SoundPicker/res/values-it/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossibile aggiungere suoneria personalizzata"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossibile eliminare suoneria personalizzata"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"L\'elenco è vuoto"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suoneria"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrazione"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-iw/strings.xml b/packages/SoundPicker/res/values-iw/strings.xml
index 238370c9..387b140 100644
--- a/packages/SoundPicker/res/values-iw/strings.xml
+++ b/packages/SoundPicker/res/values-iw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"לא ניתן להוסיף רינגטון מותאם אישית"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"לא ניתן למחוק רינגטון מותאם אישית"</string>
<string name="app_label" msgid="3091611356093417332">"צלילים"</string>
- <string name="empty_list" msgid="2871978423955821191">"הרשימה ריקה"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"צליל"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"רטט"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ja/strings.xml b/packages/SoundPicker/res/values-ja/strings.xml
index 408e870..7c2aec6 100644
--- a/packages/SoundPicker/res/values-ja/strings.xml
+++ b/packages/SoundPicker/res/values-ja/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"カスタム着信音を追加できません"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"カスタム着信音を削除できません"</string>
<string name="app_label" msgid="3091611356093417332">"サウンド"</string>
- <string name="empty_list" msgid="2871978423955821191">"このリストは空です"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"バイブレーション"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ka/strings.xml b/packages/SoundPicker/res/values-ka/strings.xml
index 83dfc3c..1cfe240 100644
--- a/packages/SoundPicker/res/values-ka/strings.xml
+++ b/packages/SoundPicker/res/values-ka/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"მორგებული ზარის დამატება შეუძლებელია"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"მორგებული ზარის წაშლა შეუძლებელია"</string>
<string name="app_label" msgid="3091611356093417332">"ხმები"</string>
- <string name="empty_list" msgid="2871978423955821191">"სია ცარიელია"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ხმა"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ვიბრაცია"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kk/strings.xml b/packages/SoundPicker/res/values-kk/strings.xml
index 1815a95..8c4c169 100644
--- a/packages/SoundPicker/res/values-kk/strings.xml
+++ b/packages/SoundPicker/res/values-kk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Арнаулы рингтонды енгізу мүмкін емес"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Арнаулы рингтонды жою мүмкін емес"</string>
<string name="app_label" msgid="3091611356093417332">"Дыбыстар"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тізім бос."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дыбыс"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Діріл"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-km/strings.xml b/packages/SoundPicker/res/values-km/strings.xml
index 6ae048f..a334429 100644
--- a/packages/SoundPicker/res/values-km/strings.xml
+++ b/packages/SoundPicker/res/values-km/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"មិនអាចបន្ថែមសំឡេងរោទ៍ផ្ទាល់ខ្លួនបាន"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"មិនអាចលុបសំឡេងរោទ៍ផ្ទាល់ខ្លួនបានទេ"</string>
<string name="app_label" msgid="3091611356093417332">"សំឡេង"</string>
- <string name="empty_list" msgid="2871978423955821191">"បញ្ជីគឺទទេ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"សំឡេង"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ការញ័រ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kn/strings.xml b/packages/SoundPicker/res/values-kn/strings.xml
index c528866..da90ccb 100644
--- a/packages/SoundPicker/res/values-kn/strings.xml
+++ b/packages/SoundPicker/res/values-kn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ಕಸ್ಟಮ್ ರಿಂಗ್ಟೋನ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ಕಸ್ಟಮ್ ರಿಂಗ್ಟೋನ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="app_label" msgid="3091611356093417332">"ಧ್ವನಿಗಳು"</string>
- <string name="empty_list" msgid="2871978423955821191">"ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದೆ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ಧ್ವನಿ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ವೈಬ್ರೇಷನ್"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ko/strings.xml b/packages/SoundPicker/res/values-ko/strings.xml
index bcab6b2..70554d6 100644
--- a/packages/SoundPicker/res/values-ko/strings.xml
+++ b/packages/SoundPicker/res/values-ko/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"맞춤 벨소리를 추가할 수 없습니다."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"맞춤 벨소리를 삭제할 수 없습니다."</string>
<string name="app_label" msgid="3091611356093417332">"소리"</string>
- <string name="empty_list" msgid="2871978423955821191">"목록이 비어 있음"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"소리"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"진동"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ky/strings.xml b/packages/SoundPicker/res/values-ky/strings.xml
index babd8c0..3c95228 100644
--- a/packages/SoundPicker/res/values-ky/strings.xml
+++ b/packages/SoundPicker/res/values-ky/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Жеке рингтон кошулбай жатат"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Жеке рингтон жок кылынбай жатат"</string>
<string name="app_label" msgid="3091611356093417332">"Үндөр"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тизме бош"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Үн"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Дирилдөө"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lo/strings.xml b/packages/SoundPicker/res/values-lo/strings.xml
index 5e496e8..8bcae0d 100644
--- a/packages/SoundPicker/res/values-lo/strings.xml
+++ b/packages/SoundPicker/res/values-lo/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"ສຽງ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ລາຍຊື່ຫວ່າງເປົ່າ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ສຽງ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ການສັ່ນເຕືອນ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lt/strings.xml b/packages/SoundPicker/res/values-lt/strings.xml
index c68cc3b..c7ea369 100644
--- a/packages/SoundPicker/res/values-lt/strings.xml
+++ b/packages/SoundPicker/res/values-lt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepavyksta pridėti tinkinto skambėjimo tono"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepavyksta ištrinti tinkinto skambėjimo tono"</string>
<string name="app_label" msgid="3091611356093417332">"Garsai"</string>
- <string name="empty_list" msgid="2871978423955821191">"Sąrašas yra tuščias"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Garsas"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibravimas"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lv/strings.xml b/packages/SoundPicker/res/values-lv/strings.xml
index fab7f8c..2a26289 100644
--- a/packages/SoundPicker/res/values-lv/strings.xml
+++ b/packages/SoundPicker/res/values-lv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nevar pievienot pielāgotu zvana signālu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nevar izdzēst pielāgotu zvana signālu"</string>
<string name="app_label" msgid="3091611356093417332">"Skaņas"</string>
- <string name="empty_list" msgid="2871978423955821191">"Saraksts ir tukšs"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Skaņa"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrācija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mk/strings.xml b/packages/SoundPicker/res/values-mk/strings.xml
index 4f2322a..545d5ed 100644
--- a/packages/SoundPicker/res/values-mk/strings.xml
+++ b/packages/SoundPicker/res/values-mk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не може да се додаде приспособена мелодија"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не може да се избрише приспособена мелодија"</string>
<string name="app_label" msgid="3091611356093417332">"Звуци"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списокот е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрации"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ml/strings.xml b/packages/SoundPicker/res/values-ml/strings.xml
index 16cf725..21da8e8 100644
--- a/packages/SoundPicker/res/values-ml/strings.xml
+++ b/packages/SoundPicker/res/values-ml/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ഇഷ്ടാനുസൃത റിംഗ്ടോൺ ചേർക്കാനാവില്ല"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ഇഷ്ടാനുസൃത റിംഗ്ടോൺ ഇല്ലാതാക്കാനാവില്ല"</string>
<string name="app_label" msgid="3091611356093417332">"ശബ്ദങ്ങൾ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ലിസ്റ്റിൽ ഒന്നുമില്ല"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ശബ്ദം"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"വൈബ്രേഷൻ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mn/strings.xml b/packages/SoundPicker/res/values-mn/strings.xml
index 6215a41..15f7d12 100644
--- a/packages/SoundPicker/res/values-mn/strings.xml
+++ b/packages/SoundPicker/res/values-mn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Захиалгат хонхны ая нэмэх боломжгүй"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Захиалгат хонхны ая устгах боломжгүй"</string>
<string name="app_label" msgid="3091611356093417332">"Дуу чимээ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Жагсаалт хоосон байна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дуу"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Чичиргээ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mr/strings.xml b/packages/SoundPicker/res/values-mr/strings.xml
index fe2fe12..3ddb991 100644
--- a/packages/SoundPicker/res/values-mr/strings.xml
+++ b/packages/SoundPicker/res/values-mr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"कस्टम रिंगटोन जोडण्यात अक्षम"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"कस्टम रिंगटोन हटविण्यात अक्षम"</string>
<string name="app_label" msgid="3091611356093417332">"आवाज"</string>
- <string name="empty_list" msgid="2871978423955821191">"ही सूची रिकामी आहे"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"आवाज"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"व्हायब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ms/strings.xml b/packages/SoundPicker/res/values-ms/strings.xml
index 5788f18..9d87d72 100644
--- a/packages/SoundPicker/res/values-ms/strings.xml
+++ b/packages/SoundPicker/res/values-ms/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambah nada dering tersuai"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat memadamkan nada dering tersuai"</string>
<string name="app_label" msgid="3091611356093417332">"Bunyi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Senarai ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Bunyi"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-my/strings.xml b/packages/SoundPicker/res/values-my/strings.xml
index 0a1a4cf..62163e9 100644
--- a/packages/SoundPicker/res/values-my/strings.xml
+++ b/packages/SoundPicker/res/values-my/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ထည့်သွင်း၍မရပါ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ဖျက်၍မရပါ"</string>
<string name="app_label" msgid="3091611356093417332">"အသံများ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ဤစာရင်းတွင် မည်သည့်အရာမျှမရှိပါ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"အသံ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"တုန်ခါမှု"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nb/strings.xml b/packages/SoundPicker/res/values-nb/strings.xml
index c315801..e4e259a 100644
--- a/packages/SoundPicker/res/values-nb/strings.xml
+++ b/packages/SoundPicker/res/values-nb/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan ikke legge til egendefinert ringelyd"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan ikke slette egendefinert ringelyd"</string>
<string name="app_label" msgid="3091611356093417332">"Lyder"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrering"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ne/strings.xml b/packages/SoundPicker/res/values-ne/strings.xml
index b95b70c..0a2bceb 100644
--- a/packages/SoundPicker/res/values-ne/strings.xml
+++ b/packages/SoundPicker/res/values-ne/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आफू अनुकूल रिङटोन थप्न सकिएन"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आफू अनुकूल रिङटोनलाई मेट्न सकिएन"</string>
<string name="app_label" msgid="3091611356093417332">"ध्वनिहरू"</string>
- <string name="empty_list" msgid="2871978423955821191">"यो सूची खाली छ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ध्वनि"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"भाइब्रेसन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nl/strings.xml b/packages/SoundPicker/res/values-nl/strings.xml
index 192431b..5b6fb70 100644
--- a/packages/SoundPicker/res/values-nl/strings.xml
+++ b/packages/SoundPicker/res/values-nl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Toevoegen van aangepaste ringtone is mislukt"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Verwijderen van aangepaste ringtone is mislukt"</string>
<string name="app_label" msgid="3091611356093417332">"Geluiden"</string>
- <string name="empty_list" msgid="2871978423955821191">"De lijst is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Geluid"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Trillen"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-or/strings.xml b/packages/SoundPicker/res/values-or/strings.xml
index 5d82039..45ce594 100644
--- a/packages/SoundPicker/res/values-or/strings.xml
+++ b/packages/SoundPicker/res/values-or/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"କଷ୍ଟମ୍ ରିଙ୍ଗଟୋନ୍ ଯୋଡ଼ିପାରିବ ନାହିଁ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"କଷ୍ଟମ୍ ରିଙ୍ଗଟୋନ୍ ଡିଲିଟ୍ କରିପାରିବ ନାହିଁ"</string>
<string name="app_label" msgid="3091611356093417332">"ସାଉଣ୍ଡ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ତାଲିକା ଖାଲି ଅଛି"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ସାଉଣ୍ଡ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ଭାଇବ୍ରେସନ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pa/strings.xml b/packages/SoundPicker/res/values-pa/strings.xml
index 63ecb9d..1e62f64 100644
--- a/packages/SoundPicker/res/values-pa/strings.xml
+++ b/packages/SoundPicker/res/values-pa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਅਯੋਗ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇ ਅਯੋਗ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"ਇਹ ਸੂਚੀ ਖਾਲੀ ਹੈ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ਅਵਾਜ਼"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ਥਰਥਰਾਹਟ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pl/strings.xml b/packages/SoundPicker/res/values-pl/strings.xml
index 310f40f..1b3b5c4 100644
--- a/packages/SoundPicker/res/values-pl/strings.xml
+++ b/packages/SoundPicker/res/values-pl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nie można dodać dzwonka niestandardowego"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nie można usunąć dzwonka niestandardowego"</string>
<string name="app_label" msgid="3091611356093417332">"Dźwięki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista jest pusta"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Dźwięk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Wibracje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rBR/strings.xml b/packages/SoundPicker/res/values-pt-rBR/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rBR/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rPT/strings.xml b/packages/SoundPicker/res/values-pt-rPT/strings.xml
index fd4e69f..5d742f1 100644
--- a/packages/SoundPicker/res/values-pt-rPT/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rPT/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível eliminar o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt/strings.xml b/packages/SoundPicker/res/values-pt/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt/strings.xml
+++ b/packages/SoundPicker/res/values-pt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ro/strings.xml b/packages/SoundPicker/res/values-ro/strings.xml
index 08d937c..58b5aeb 100644
--- a/packages/SoundPicker/res/values-ro/strings.xml
+++ b/packages/SoundPicker/res/values-ro/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nu se poate adăuga tonul de sonerie personalizat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nu se poate șterge tonul de sonerie personalizat"</string>
<string name="app_label" msgid="3091611356093417332">"Sunete"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista este goală"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sunet"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrație"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ru/strings.xml b/packages/SoundPicker/res/values-ru/strings.xml
index be5495a..0d48ac1 100644
--- a/packages/SoundPicker/res/values-ru/strings.xml
+++ b/packages/SoundPicker/res/values-ru/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не удалось добавить рингтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не удалось удалить рингтон"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пуст"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрация"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-si/strings.xml b/packages/SoundPicker/res/values-si/strings.xml
index 6ba86cb..1872b6b 100644
--- a/packages/SoundPicker/res/values-si/strings.xml
+++ b/packages/SoundPicker/res/values-si/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"අභිරුචි නාද රිද්මය එක් කළ නොහැකිය"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"අභිරුචි නාද රිද්මය මැකිය නොහැකිය"</string>
<string name="app_label" msgid="3091611356093417332">"ශබ්ද"</string>
- <string name="empty_list" msgid="2871978423955821191">"ලැයිස්තුව හිස් ය"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ශබ්දය"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"කම්පනය"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml
index 8f54350..8ff6d12 100644
--- a/packages/SoundPicker/res/values-sk/strings.xml
+++ b/packages/SoundPicker/res/values-sk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepodarilo sa pridať vlastný tón zvonenia"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepodarilo sa odstrániť vlastný tón zvonenia"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zoznam je prázdny"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrácie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sl/strings.xml b/packages/SoundPicker/res/values-sl/strings.xml
index 1a818b2..77a2a2c 100644
--- a/packages/SoundPicker/res/values-sl/strings.xml
+++ b/packages/SoundPicker/res/values-sl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tona zvonjenja po meri ni mogoče dodati"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tona zvonjenja po meri ni mogoče izbrisati"</string>
<string name="app_label" msgid="3091611356093417332">"Zvoki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prazen"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvok"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sq/strings.xml b/packages/SoundPicker/res/values-sq/strings.xml
index 4c497bb..e35dd71 100644
--- a/packages/SoundPicker/res/values-sq/strings.xml
+++ b/packages/SoundPicker/res/values-sq/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nuk mund të shtojë ton zileje të personalizuar"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nuk mund të fshijë ton zileje të personalizuar"</string>
<string name="app_label" msgid="3091611356093417332">"Tingujt"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista është bosh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Me tingull"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Me dridhje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sr/strings.xml b/packages/SoundPicker/res/values-sr/strings.xml
index 2ec1f17..bc573f5 100644
--- a/packages/SoundPicker/res/values-sr/strings.xml
+++ b/packages/SoundPicker/res/values-sr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Додавање прилагођене мелодије звона није успело"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Брисање прилагођене мелодије звона није успело"</string>
<string name="app_label" msgid="3091611356093417332">"Звукови"</string>
- <string name="empty_list" msgid="2871978423955821191">"Листа је празна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрирање"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sv/strings.xml b/packages/SoundPicker/res/values-sv/strings.xml
index cb1cd3a..c1dd1c2 100644
--- a/packages/SoundPicker/res/values-sv/strings.xml
+++ b/packages/SoundPicker/res/values-sv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Det gick inte att lägga till en egen ringsignal"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Det gick inte att radera den egna ringsignalen"</string>
<string name="app_label" msgid="3091611356093417332">"Ljud"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listan är tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ljud"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sw/strings.xml b/packages/SoundPicker/res/values-sw/strings.xml
index 8fd6d58..b023450 100644
--- a/packages/SoundPicker/res/values-sw/strings.xml
+++ b/packages/SoundPicker/res/values-sw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Imeshindwa kuongeza mlio maalum wa simu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Imeshindwa kufuta mlio maalum wa simu"</string>
<string name="app_label" msgid="3091611356093417332">"Sauti"</string>
- <string name="empty_list" msgid="2871978423955821191">"Orodha hii haina chochote"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sauti"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Mtetemo"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ta/strings.xml b/packages/SoundPicker/res/values-ta/strings.xml
index 692e58a..38e45b7 100644
--- a/packages/SoundPicker/res/values-ta/strings.xml
+++ b/packages/SoundPicker/res/values-ta/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"பிரத்தியேக ரிங்டோனைச் சேர்க்க முடியவில்லை"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"பிரத்தியேக ரிங்டோனை நீக்க முடியவில்லை"</string>
<string name="app_label" msgid="3091611356093417332">"ஒலிகள்"</string>
- <string name="empty_list" msgid="2871978423955821191">"பட்டியல் காலியாக உள்ளது"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ஒலி"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"அதிர்வு"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index ce13e53..2d03ac0 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్టోన్ను జోడించలేకపోయింది"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్టోన్ను తొలగించలేకపోయింది"</string>
<string name="app_label" msgid="3091611356093417332">"ధ్వనులు"</string>
- <string name="empty_list" msgid="2871978423955821191">"మీ లిస్ట్ ఖాళీగా ఉంది"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"సౌండ్"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"వైబ్రేషన్"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-th/strings.xml b/packages/SoundPicker/res/values-th/strings.xml
index d5dc9b7..cc2e43f 100644
--- a/packages/SoundPicker/res/values-th/strings.xml
+++ b/packages/SoundPicker/res/values-th/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ไม่สามารถเพิ่มเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ไม่สามารถลบเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="app_label" msgid="3091611356093417332">"เสียง"</string>
- <string name="empty_list" msgid="2871978423955821191">"รายการว่างเปล่า"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"เสียง"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"การสั่น"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tl/strings.xml b/packages/SoundPicker/res/values-tl/strings.xml
index 4e3bf7e..c0c1712 100644
--- a/packages/SoundPicker/res/values-tl/strings.xml
+++ b/packages/SoundPicker/res/values-tl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Hindi maidagdag ang custom na ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Hindi ma-delete ang custom na ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Mga Tunog"</string>
- <string name="empty_list" msgid="2871978423955821191">"Walang laman ang listahan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tunog"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Pag-vibrate"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tr/strings.xml b/packages/SoundPicker/res/values-tr/strings.xml
index 51ac541..955c23f 100644
--- a/packages/SoundPicker/res/values-tr/strings.xml
+++ b/packages/SoundPicker/res/values-tr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Özel zil sesi eklenemiyor"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Özel zil sesi silinemiyor"</string>
<string name="app_label" msgid="3091611356093417332">"Sesler"</string>
- <string name="empty_list" msgid="2871978423955821191">"Liste boş"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ses"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titreşim"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uk/strings.xml b/packages/SoundPicker/res/values-uk/strings.xml
index a905b95..42dbfb0 100644
--- a/packages/SoundPicker/res/values-uk/strings.xml
+++ b/packages/SoundPicker/res/values-uk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не вдалося додати користувацький сигнал дзвінка"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не вдалося видалити користувацький сигнал дзвінка"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пустий"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрація"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ur/strings.xml b/packages/SoundPicker/res/values-ur/strings.xml
index 9cae118..58141d6 100644
--- a/packages/SoundPicker/res/values-ur/strings.xml
+++ b/packages/SoundPicker/res/values-ur/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"حسب ضرورت رنگ ٹون شامل کرنے سے قاصر ہے"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حسب ضرورت رنگ ٹون حذف کرنے سے قاصر ہے"</string>
<string name="app_label" msgid="3091611356093417332">"آوازیں"</string>
- <string name="empty_list" msgid="2871978423955821191">"فہرست خالی ہے"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"آواز"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"وائبریشن"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index e6231f2..c39db5f 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
<string name="app_label" msgid="3091611356093417332">"Tovushlar"</string>
- <string name="empty_list" msgid="2871978423955821191">"Roʻyxat boʻsh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tovush"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Tebranish"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-vi/strings.xml b/packages/SoundPicker/res/values-vi/strings.xml
index 070ac16..bed0e96 100644
--- a/packages/SoundPicker/res/values-vi/strings.xml
+++ b/packages/SoundPicker/res/values-vi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Không thể thêm nhạc chuông tùy chỉnh"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Không thể xóa nhạc chuông tùy chỉnh"</string>
<string name="app_label" msgid="3091611356093417332">"Âm thanh"</string>
- <string name="empty_list" msgid="2871978423955821191">"Danh sách này đang trống"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Âm thanh"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rung"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rCN/strings.xml b/packages/SoundPicker/res/values-zh-rCN/strings.xml
index 0420fc9..864aaae 100644
--- a/packages/SoundPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rCN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"无法添加自定义铃声"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"无法删除自定义铃声"</string>
<string name="app_label" msgid="3091611356093417332">"声音"</string>
- <string name="empty_list" msgid="2871978423955821191">"列表为空"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"声音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"振动"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rHK/strings.xml b/packages/SoundPicker/res/values-zh-rHK/strings.xml
index 60d9a52..4cde32d 100644
--- a/packages/SoundPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rHK/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法加入自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有內容"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rTW/strings.xml b/packages/SoundPicker/res/values-zh-rTW/strings.xml
index c173c0a..df8a66a 100644
--- a/packages/SoundPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rTW/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法新增自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有任何項目"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zu/strings.xml b/packages/SoundPicker/res/values-zu/strings.xml
index 978e925..29a8ffe 100644
--- a/packages/SoundPicker/res/values-zu/strings.xml
+++ b/packages/SoundPicker/res/values-zu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ayikwazi ukwengeza ithoni yokukhala yangokwezifiso"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ayikwazi ukususa ithoni yokukhala yangokwezifiso"</string>
<string name="app_label" msgid="3091611356093417332">"Imisindo"</string>
- <string name="empty_list" msgid="2871978423955821191">"Uhlu alunalutho"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Umsindo"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Ukudlidliza"</string>
</resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d3200d..17cc9f8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -214,6 +214,7 @@
lint: {
extra_check_modules: ["SystemUILintChecker"],
+ warning_checks: ["MissingApacheLicenseDetector"],
},
}
@@ -254,7 +255,6 @@
srcs: [
/* Keyguard converted tests */
// data
- "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -402,10 +402,19 @@
"tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt",
"tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt",
"tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt",
+
],
path: "tests/src",
}
+filegroup {
+ name: "SystemUI-tests-multivalent",
+ srcs: [
+ "multivalentTests/src/**/*.kt",
+ ],
+ path: "multivalentTests/src",
+}
+
java_library {
name: "SystemUI-tests-concurrency",
srcs: [
@@ -476,6 +485,8 @@
"motion_tool_lib",
"androidx.core_core-animation-testing-nodeps",
"androidx.compose.ui_ui",
+ "flag-junit",
+ "platform-test-annotations",
],
}
@@ -494,6 +505,7 @@
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-multivalent",
":SystemUI-tests-utils",
],
static_libs: [
@@ -572,6 +584,7 @@
":SystemUI-tests-utils",
":SystemUI-test-fakes",
":SystemUI-tests-robolectric-pilots",
+ ":SystemUI-tests-multivalent",
],
static_libs: [
"androidx.test.uiautomator_uiautomator",
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 8f329b3..34545cf 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -36,6 +36,7 @@
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 @@
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 @@
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/accessibility/accessibilitymenu/res/values-hy/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
index e06787f..66e37a1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
@@ -10,7 +10,7 @@
<string name="power_utterance" msgid="7444296686402104807">"Սնուցման կոճակի ընտրանքներ"</string>
<string name="recent_apps_label" msgid="6583276995616385847">"Վերջին հավելվածներ"</string>
<string name="lockscreen_label" msgid="648347953557887087">"Կողպէկրան"</string>
- <string name="quick_settings_label" msgid="2999117381487601865">"Արագ\\nկարգավորումներ"</string>
+ <string name="quick_settings_label" msgid="2999117381487601865">"Արագ կարգավորումներ"</string>
<string name="notifications_label" msgid="6829741046963013567">"Ծանուցումներ"</string>
<string name="screenshot_label" msgid="863978141223970162">"Սքրինշոթ"</string>
<string name="screenshot_utterance" msgid="1430760563401895074">"Ստանալ սքրինշոթը"</string>
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index bcf1535..08ecf09b 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -8,3 +8,10 @@
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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0567528..c26d5f5 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -23,6 +23,14 @@
}
flag {
+ name: "notifications_icon_container_refactor"
+ namespace: "systemui"
+ description: "Enables the refactored version of the notification icon container in StatusBar, "
+ "AOD, and the notification shelf. Should not bring any behavioral changes."
+ bug: "278765923"
+}
+
+flag {
name: "notification_lifetime_extension_refactor"
namespace: "systemui"
description: "Enables moving notification lifetime extension management from SystemUI to "
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 4ea57a8..ab4db45 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 @@
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/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 0000000..46125be
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ val firstComment = node.allCommentsInFile.firstOrNull()
+ // Normally we don't need to explicitly handle suppressing case and just return
+ // error as usual with indicating node and lint will ignore it for us. But here
+ // suppressing will be applied on top of comment that doesn't exist so we don't have
+ // node to return - it's a bit of corner case
+ if (firstComment != null && firstComment.isSuppressingComment()) {
+ return
+ }
+ if (firstComment == null || !firstComment.isLicenseComment()) {
+ val firstLineOfFile =
+ Location.create(
+ context.file,
+ SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+ )
+ context.report(
+ issue = ISSUE,
+ location = firstLineOfFile,
+ message =
+ "License header is missing\n" +
+ "Please add the following copyright and license header to the" +
+ " beginning of the file:\n\n" +
+ copyrightHeader
+ )
+ }
+ }
+ }
+ }
+
+ private fun UComment.isSuppressingComment(): Boolean {
+ val suppressingComment =
+ "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+ return text.contains(suppressingComment)
+ }
+
+ private fun UComment.isLicenseComment(): Boolean {
+ // We probably don't want to compare full copyright header in case there are some small
+ // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+ // good enough if this detector deals with missing
+ // license header instead of incorrect license header
+ return text.contains("Apache License")
+ }
+
+ private val copyrightHeader: String
+ get() =
+ """
+ /*
+ * Copyright (C) ${Year.now().value} 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.
+ */
+ """
+ .trimIndent()
+ .trim()
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "MissingApacheLicenseDetector",
+ briefDescription = "File is missing Apache license information",
+ explanation =
+ """
+ Every source code file should have copyright and license information \
+ attached at the beginning.""",
+ category = Category.COMPLIANCE,
+ priority = 8,
+ // ignored by default and then explicitly overridden in SysUI's soong configuration
+ severity = Severity.IGNORE,
+ implementation =
+ Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 520c888..e09aa42 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -42,6 +42,7 @@
StaticSettingsProviderDetector.ISSUE,
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
+ MissingApacheLicenseDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 0000000..78e133f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return MissingApacheLicenseDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ MissingApacheLicenseDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun testHasCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ /*
+ * Copyright (C) ${Year.now().value} 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 test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testDoesntHaveCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+ // probably doesn't make much sense for license header (which is far above it) and for
+ // kotlin files that can have several classes. If someone really wants to omit header
+ // they can do it with //noinspection
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectContains("License header is missing")
+ }
+}
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 df87d19d..91fe33c 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 @@
*
* 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 @@
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 c1974ca..50b7c5f 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 @@
),
)
- assertDistribution(cards, expected)
+ assertDistributionBySize(cards, expected)
}
@Test
@@ -71,10 +71,30 @@
),
)
- 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 @@
}
}
+ 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 @@
}
}
}
+
+ 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 17c74ba..0e7694e 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 @@
}
}
- 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 689a0a2..eb71490 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.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.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.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 @@
) {
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 @@
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 @@
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/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
new file mode 100644
index 0000000..d005413
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -0,0 +1,20 @@
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import kotlinx.coroutines.CoroutineScope
+
+interface GestureHandler {
+ val draggable: DraggableHandler
+ val nestedScroll: NestedScrollHandler
+}
+
+interface DraggableHandler {
+ suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset)
+ fun onDelta(pixels: Float)
+ suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float)
+}
+
+interface NestedScrollHandler {
+ val connection: NestedScrollConnection
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 3fd6828..9c799b28 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@@ -100,3 +101,19 @@
MovableElement(layoutImpl, scene, key, modifier, content)
}
}
+
+/** The destination scene when swiping up or left from [upOrLeft]. */
+internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
+ return when (orientation) {
+ Orientation.Vertical -> userActions[Swipe.Up]
+ Orientation.Horizontal -> userActions[Swipe.Left]
+ }
+}
+
+/** The destination scene when swiping down or right from [downOrRight]. */
+internal fun Scene.downOrRight(orientation: Orientation): SceneKey? {
+ return when (orientation) {
+ Orientation.Vertical -> userActions[Swipe.Down]
+ Orientation.Horizontal -> userActions[Swipe.Right]
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 4952270..a40b299 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.activity.compose.BackHandler
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -39,7 +40,8 @@
import com.android.compose.ui.util.fastForEach
import kotlinx.coroutines.channels.Channel
-internal class SceneTransitionLayoutImpl(
+@VisibleForTesting
+class SceneTransitionLayoutImpl(
onChangeScene: (SceneKey) -> Unit,
builder: SceneTransitionLayoutScope.() -> Unit,
transitions: SceneTransitions,
@@ -60,7 +62,7 @@
* The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
* any scene configured or right before the first measure pass of the layout.
*/
- internal var size by mutableStateOf(IntSize.Zero)
+ @VisibleForTesting var size by mutableStateOf(IntSize.Zero)
init {
setScenes(builder)
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 1cbfe30..2dc53ab 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
@@ -16,10 +16,11 @@
package com.android.compose.animation.scene
+import android.util.Log
+import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
-import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
@@ -34,10 +35,9 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
-import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -51,417 +51,380 @@
layoutImpl: SceneTransitionLayoutImpl,
orientation: Orientation,
): Modifier {
- val state = layoutImpl.state.transitionState
- val currentScene = layoutImpl.scene(state.currentScene)
- val transition = remember {
- // Note that the currentScene here does not matter, it's only used for initializing the
- // transition and will be replaced when a drag event starts.
- SwipeTransition(initialScene = currentScene)
- }
+ val gestureHandler = rememberSceneGestureHandler(layoutImpl, orientation)
- val enabled = state == transition || currentScene.shouldEnableSwipes(orientation)
+ /** Whether swipe should be enabled in the given [orientation]. */
+ fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
+ upOrLeft(orientation) != null || downOrRight(orientation) != null
- // Immediately start the drag if this our [transition] is currently animating to a scene (i.e.
- // the user released their input pointer after swiping in this orientation) and the user can't
- // swipe in the other direction.
- val startDragImmediately =
- state == transition &&
- transition.isAnimatingOffset &&
- !currentScene.shouldEnableSwipes(orientation.opposite())
-
- // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
- // as SwipeableV2Defaults.VelocityThreshold.
- val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() }
-
- // The positional threshold at which the intent of the user is to swipe to the next scene. It is
- // the same as SwipeableV2Defaults.PositionalThreshold.
- val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
-
- val draggableState = rememberDraggableState { delta ->
- onDrag(layoutImpl, transition, orientation, delta)
- }
-
- return nestedScroll(
- connection =
- rememberSwipeToSceneNestedScrollConnection(
- orientation = orientation,
- coroutineScope = rememberCoroutineScope(),
- draggableState = draggableState,
- transition = transition,
- layoutImpl = layoutImpl,
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold
- ),
+ val currentScene = gestureHandler.currentScene
+ val canSwipe = currentScene.shouldEnableSwipes(orientation)
+ val canOppositeSwipe =
+ currentScene.shouldEnableSwipes(
+ when (orientation) {
+ Orientation.Vertical -> Orientation.Horizontal
+ Orientation.Horizontal -> Orientation.Vertical
+ }
)
+
+ return nestedScroll(connection = gestureHandler.nestedScroll.connection)
.draggable(
- state = draggableState,
+ state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta),
orientation = orientation,
- enabled = enabled,
- startDragImmediately = startDragImmediately,
- onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
- onDragStopped = { velocity ->
- onDragStopped(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocity,
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold,
- )
- },
+ enabled = gestureHandler.isDrivingTransition || canSwipe,
+ // Immediately start the drag if this our [transition] is currently animating to a scene
+ // (i.e. the user released their input pointer after swiping in this orientation) and
+ // the user can't swipe in the other direction.
+ startDragImmediately =
+ gestureHandler.isDrivingTransition &&
+ gestureHandler.isAnimatingOffset &&
+ !canOppositeSwipe,
+ onDragStarted = gestureHandler.draggable::onDragStarted,
+ onDragStopped = gestureHandler.draggable::onDragStopped,
)
}
-private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
- var _currentScene by mutableStateOf(initialScene)
- override val currentScene: SceneKey
- get() = _currentScene.key
+@Composable
+private fun rememberSceneGestureHandler(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+): SceneGestureHandler {
+ val coroutineScope = rememberCoroutineScope()
- var _fromScene by mutableStateOf(initialScene)
- override val fromScene: SceneKey
- get() = _fromScene.key
+ val gestureHandler =
+ remember(layoutImpl, orientation, coroutineScope) {
+ SceneGestureHandler(layoutImpl, orientation, coroutineScope)
+ }
- var _toScene by mutableStateOf(initialScene)
- override val toScene: SceneKey
- get() = _toScene.key
+ // Make sure we reset the scroll connection when this handler is removed from composition
+ val connection = gestureHandler.nestedScroll.connection
+ DisposableEffect(connection) { onDispose { connection.reset() } }
- override val progress: Float
- get() {
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
- if (distance == 0f) {
- // This can happen only if fromScene == toScene.
- error(
- "Transition.progress should be called only when Transition.fromScene != " +
- "Transition.toScene"
+ return gestureHandler
+}
+
+@VisibleForTesting
+class SceneGestureHandler(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ internal val orientation: Orientation,
+ private val coroutineScope: CoroutineScope,
+) : GestureHandler {
+ override val draggable: DraggableHandler = SceneDraggableHandler(this)
+
+ override val nestedScroll: SceneNestedScrollHandler = SceneNestedScrollHandler(this)
+
+ private var transitionState
+ get() = layoutImpl.state.transitionState
+ set(value) {
+ layoutImpl.state.transitionState = value
+ }
+
+ /**
+ * The transition controlled by this gesture handler. It will be set as the [transitionState] in
+ * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
+ *
+ * Note: the initialScene here does not matter, it's only used for initializing the transition
+ * and will be replaced when a drag event starts.
+ */
+ private val swipeTransition = SwipeTransition(initialScene = currentScene)
+
+ internal val currentScene: Scene
+ get() = layoutImpl.scene(transitionState.currentScene)
+
+ @VisibleForTesting
+ val isDrivingTransition
+ get() = transitionState == swipeTransition
+
+ @VisibleForTesting
+ var isAnimatingOffset
+ get() = swipeTransition.isAnimatingOffset
+ private set(value) {
+ swipeTransition.isAnimatingOffset = value
+ }
+
+ internal val swipeTransitionToScene
+ get() = swipeTransition._toScene
+
+ /**
+ * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
+ * as SwipeableV2Defaults.VelocityThreshold.
+ */
+ @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+
+ /**
+ * The positional threshold at which the intent of the user is to swipe to the next scene. It is
+ * the same as SwipeableV2Defaults.PositionalThreshold.
+ */
+ 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.
+ // Stop animating and start from where the current offset.
+ swipeTransition.stopOffsetAnimation()
+ return
+ }
+
+ val transition = transitionState
+ if (transition is TransitionState.Transition) {
+ // TODO(b/290184746): Better handle interruptions here if state != idle.
+ Log.w(
+ TAG,
+ "start from TransitionState.Transition is not fully supported: from" +
+ " ${transition.fromScene} to ${transition.toScene} " +
+ "(progress ${transition.progress})"
+ )
+ }
+
+ val fromScene = currentScene
+
+ swipeTransition._currentScene = fromScene
+ swipeTransition._fromScene = fromScene
+
+ // We don't know where we are transitioning to yet given that the drag just started, so set
+ // it to fromScene, which will effectively be treated the same as Idle(fromScene).
+ swipeTransition._toScene = fromScene
+
+ swipeTransition.stopOffsetAnimation()
+ swipeTransition.dragOffset = 0f
+
+ // Use the layout size in the swipe orientation for swipe distance.
+ // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances,
+ // we will also have to make sure that we correctly handle overscroll.
+ swipeTransition.absoluteDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
+
+ if (swipeTransition.absoluteDistance > 0f) {
+ transitionState = swipeTransition
+ }
+ }
+
+ internal fun onDrag(delta: Float) {
+ if (delta == 0f) return
+
+ swipeTransition.dragOffset += delta
+
+ // First check transition.fromScene should be changed for the case where the user quickly
+ // swiped twice in a row to accelerate the transition and go from A => B then B => C really
+ // fast.
+ maybeHandleAcceleratedSwipe()
+
+ val offset = swipeTransition.dragOffset
+ val fromScene = swipeTransition._fromScene
+
+ // Compute the target scene depending on the current offset.
+ val target = fromScene.findTargetSceneAndDistance(offset)
+
+ if (swipeTransition._toScene.key != target.sceneKey) {
+ swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
+ }
+
+ if (swipeTransition._distance != target.distance) {
+ swipeTransition._distance = target.distance
+ }
+ }
+
+ /**
+ * Change fromScene in the case where the user quickly swiped multiple times in the same
+ * direction to accelerate the transition from A => B then B => C.
+ */
+ private fun maybeHandleAcceleratedSwipe() {
+ val toScene = swipeTransition._toScene
+ val fromScene = swipeTransition._fromScene
+
+ // If the swipe was not committed, don't do anything.
+ if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+ return
+ }
+
+ // If the offset is past the distance then let's change fromScene so that the user can swipe
+ // to the next screen or go back to the previous one.
+ val offset = swipeTransition.dragOffset
+ val absoluteDistance = swipeTransition.absoluteDistance
+ if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+ swipeTransition.dragOffset += absoluteDistance
+ swipeTransition._fromScene = toScene
+ } else if (
+ offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key
+ ) {
+ swipeTransition.dragOffset -= absoluteDistance
+ swipeTransition._fromScene = toScene
+ }
+
+ // Important note: toScene and distance will be updated right after this function is called,
+ // using fromScene and dragOffset.
+ }
+
+ private class TargetScene(
+ val sceneKey: SceneKey,
+ val distance: Float,
+ )
+
+ private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
+ val maxDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
+
+ val upOrLeft = upOrLeft(orientation)
+ val downOrRight = downOrRight(orientation)
+
+ // Compute the target scene depending on the current offset.
+ return when {
+ directionOffset < 0f && upOrLeft != null -> {
+ TargetScene(
+ sceneKey = upOrLeft,
+ distance = -maxDistance,
)
}
- return offset / distance
+ directionOffset > 0f && downOrRight != null -> {
+ TargetScene(
+ sceneKey = downOrRight,
+ distance = maxDistance,
+ )
+ }
+ else -> {
+ TargetScene(
+ sceneKey = key,
+ distance = 0f,
+ )
+ }
+ }
+ }
+
+ internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
+ // The state was changed since the drag started; don't do anything.
+ if (!isDrivingTransition) {
+ return
}
- override val isUserInputDriven = true
+ fun animateTo(targetScene: Scene, targetOffset: Float) {
+ // If the effective current scene changed, it should be reflected right now in the
+ // current scene state, even before the settle animation is ongoing. That way all the
+ // swipeables and back handlers will be refreshed and the user can for instance quickly
+ // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
+ // immediately go back B => A.
+ if (targetScene != swipeTransition._currentScene) {
+ swipeTransition._currentScene = targetScene
+ layoutImpl.onChangeScene(targetScene.key)
+ }
- /** The current offset caused by the drag gesture. */
- var dragOffset by mutableFloatStateOf(0f)
+ animateOffset(
+ initialVelocity = velocity,
+ targetOffset = targetOffset,
+ targetScene = targetScene.key
+ )
+ }
+
+ val fromScene = swipeTransition._fromScene
+ if (canChangeScene) {
+ // If we are halfway between two scenes, we check what the target will be based on the
+ // velocity and offset of the transition, then we launch the animation.
+
+ val toScene = swipeTransition._toScene
+ if (fromScene == toScene) {
+ // We were not animating.
+ transitionState = TransitionState.Idle(fromScene.key)
+ return
+ }
+
+ // Compute the destination scene (and therefore offset) to settle in.
+ val offset = swipeTransition.dragOffset
+ val distance = swipeTransition.distance
+ if (
+ shouldCommitSwipe(
+ offset,
+ distance,
+ velocity,
+ wasCommitted = swipeTransition._currentScene == toScene,
+ )
+ ) {
+ // Animate to the next scene
+ animateTo(targetScene = toScene, targetOffset = distance)
+ } else {
+ // Animate to the initial scene
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
+ } else {
+ // We are doing an overscroll animation between scenes. In this case, we can also start
+ // from the idle position.
+
+ val startFromIdlePosition = swipeTransition.dragOffset == 0f
+
+ if (startFromIdlePosition) {
+ // If there is a next scene, we start the overscroll animation.
+ val target = fromScene.findTargetSceneAndDistance(velocity)
+ val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+ if (isValidTarget) {
+ swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
+ swipeTransition._distance = target.distance
+
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ } else {
+ // We will not animate
+ transitionState = TransitionState.Idle(fromScene.key)
+ }
+ } else {
+ // We were between two scenes: animate to the initial scene.
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
+ }
+ }
/**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+ * Whether the swipe to the target scene should be committed or not. This is inspired by
+ * SwipeableV2.computeTarget().
*/
- var isAnimatingOffset by mutableStateOf(false)
-
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
-
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
-
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- fun startOffsetAnimation(job: () -> Job) {
- stopOffsetAnimation()
- offsetAnimationJob = job()
- }
-
- /** Stops any ongoing offset animation. */
- fun stopOffsetAnimation() {
- offsetAnimationJob?.cancel()
- }
-
- /** The absolute distance between [fromScene] and [toScene]. */
- var absoluteDistance = 0f
-
- /**
- * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
- * or to the left of [toScene].
- */
- var _distance by mutableFloatStateOf(0f)
- val distance: Float
- get() = _distance
-}
-
-/** The destination scene when swiping up or left from [this@upOrLeft]. */
-private fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Up]
- Orientation.Horizontal -> userActions[Swipe.Left]
- }
-}
-
-/** The destination scene when swiping down or right from [this@downOrRight]. */
-private fun Scene.downOrRight(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Down]
- Orientation.Horizontal -> userActions[Swipe.Right]
- }
-}
-
-/** Whether swipe should be enabled in the given [orientation]. */
-private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return upOrLeft(orientation) != null || downOrRight(orientation) != null
-}
-
-private fun Orientation.opposite(): Orientation {
- return when (this) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
-}
-
-private fun onDragStarted(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- orientation: Orientation,
-) {
- if (layoutImpl.state.transitionState == transition) {
- // This [transition] was already driving the animation: simply take over it.
- if (transition.isAnimatingOffset) {
- // Stop animating and start from where the current offset. Setting the animation job to
- // `null` will effectively cancel the animation.
- transition.stopOffsetAnimation()
- transition.dragOffset = transition.offsetAnimatable.value
+ private fun shouldCommitSwipe(
+ offset: Float,
+ distance: Float,
+ velocity: Float,
+ wasCommitted: Boolean,
+ ): Boolean {
+ fun isCloserToTarget(): Boolean {
+ return (offset - distance).absoluteValue < offset.absoluteValue
}
- return
- }
-
- // TODO(b/290184746): Better handle interruptions here if state != idle.
-
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-
- transition._currentScene = fromScene
- transition._fromScene = fromScene
-
- // We don't know where we are transitioning to yet given that the drag just started, so set it
- // to fromScene, which will effectively be treated the same as Idle(fromScene).
- transition._toScene = fromScene
-
- transition.stopOffsetAnimation()
- transition.dragOffset = 0f
-
- // Use the layout size in the swipe orientation for swipe distance.
- // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
- // will also have to make sure that we correctly handle overscroll.
- transition.absoluteDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
- }.toFloat()
-
- if (transition.absoluteDistance > 0f) {
- layoutImpl.state.transitionState = transition
- }
-}
-
-private fun onDrag(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- orientation: Orientation,
- delta: Float,
-) {
- transition.dragOffset += delta
-
- // First check transition.fromScene should be changed for the case where the user quickly swiped
- // twice in a row to accelerate the transition and go from A => B then B => C really fast.
- maybeHandleAcceleratedSwipe(transition, orientation)
-
- val offset = transition.dragOffset
- val fromScene = transition._fromScene
-
- // Compute the target scene depending on the current offset.
- val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
-
- if (transition._toScene.key != target.sceneKey) {
- transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
- }
-
- if (transition._distance != target.distance) {
- transition._distance = target.distance
- }
-}
-
-/**
- * Change fromScene in the case where the user quickly swiped multiple times in the same direction
- * to accelerate the transition from A => B then B => C.
- */
-private fun maybeHandleAcceleratedSwipe(
- transition: SwipeTransition,
- orientation: Orientation,
-) {
- val toScene = transition._toScene
- val fromScene = transition._fromScene
-
- // If the swipe was not committed, don't do anything.
- if (fromScene == toScene || transition._currentScene != toScene) {
- return
- }
-
- // If the offset is past the distance then let's change fromScene so that the user can swipe to
- // the next screen or go back to the previous one.
- val offset = transition.dragOffset
- val absoluteDistance = transition.absoluteDistance
- if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
- transition.dragOffset += absoluteDistance
- transition._fromScene = toScene
- } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) {
- transition.dragOffset -= absoluteDistance
- transition._fromScene = toScene
- }
-
- // Important note: toScene and distance will be updated right after this function is called,
- // using fromScene and dragOffset.
-}
-
-private data class TargetScene(
- val sceneKey: SceneKey,
- val distance: Float,
-)
-
-private fun Scene.findTargetSceneAndDistance(
- orientation: Orientation,
- directionOffset: Float,
- layoutImpl: SceneTransitionLayoutImpl,
-): TargetScene {
- val maxDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
- }.toFloat()
-
- val upOrLeft = upOrLeft(orientation)
- val downOrRight = downOrRight(orientation)
-
- // Compute the target scene depending on the current offset.
- return when {
- directionOffset < 0f && upOrLeft != null -> {
- TargetScene(
- sceneKey = upOrLeft,
- distance = -maxDistance,
- )
+ // Swiping up or left.
+ if (distance < 0f) {
+ return if (offset > 0f || velocity >= velocityThreshold) {
+ false
+ } else {
+ velocity <= -velocityThreshold ||
+ (offset <= -positionalThreshold && !wasCommitted) ||
+ isCloserToTarget()
+ }
}
- directionOffset > 0f && downOrRight != null -> {
- TargetScene(
- sceneKey = downOrRight,
- distance = maxDistance,
- )
- }
- else -> {
- TargetScene(
- sceneKey = key,
- distance = 0f,
- )
- }
- }
-}
-private fun CoroutineScope.onDragStopped(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- velocity: Float,
- velocityThreshold: Float,
- positionalThreshold: Float,
- canChangeScene: Boolean = true,
-) {
- // The state was changed since the drag started; don't do anything.
- if (layoutImpl.state.transitionState != transition) {
- return
- }
-
- // We were not animating.
- if (transition._fromScene == transition._toScene) {
- layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key)
- return
- }
-
- // Compute the destination scene (and therefore offset) to settle in.
- val targetScene: Scene
- val targetOffset: Float
- val offset = transition.dragOffset
- val distance = transition.distance
- if (
- canChangeScene &&
- shouldCommitSwipe(
- offset,
- distance,
- velocity,
- velocityThreshold,
- positionalThreshold,
- wasCommitted = transition._currentScene == transition._toScene,
- )
- ) {
- targetOffset = distance
- targetScene = transition._toScene
- } else {
- targetOffset = 0f
- targetScene = transition._fromScene
- }
-
- // If the effective current scene changed, it should be reflected right now in the current scene
- // state, even before the settle animation is ongoing. That way all the swipeables and back
- // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B
- // then horizontally from B => C, or swipe from A => B then immediately go back B => A.
- if (targetScene != transition._currentScene) {
- transition._currentScene = targetScene
- layoutImpl.onChangeScene(targetScene.key)
- }
-
- animateOffset(
- transition = transition,
- layoutImpl = layoutImpl,
- initialVelocity = velocity,
- targetOffset = targetOffset,
- targetScene = targetScene.key
- )
-}
-
-/**
- * Whether the swipe to the target scene should be committed or not. This is inspired by
- * SwipeableV2.computeTarget().
- */
-private fun shouldCommitSwipe(
- offset: Float,
- distance: Float,
- velocity: Float,
- velocityThreshold: Float,
- positionalThreshold: Float,
- wasCommitted: Boolean,
-): Boolean {
- fun isCloserToTarget(): Boolean {
- return (offset - distance).absoluteValue < offset.absoluteValue
- }
-
- // Swiping up or left.
- if (distance < 0f) {
- return if (offset > 0f || velocity >= velocityThreshold) {
+ // Swiping down or right.
+ return if (offset < 0f || velocity <= -velocityThreshold) {
false
} else {
- velocity <= -velocityThreshold ||
- (offset <= -positionalThreshold && !wasCommitted) ||
+ velocity >= velocityThreshold ||
+ (offset >= positionalThreshold && !wasCommitted) ||
isCloserToTarget()
}
}
- // Swiping down or right.
- return if (offset < 0f || velocity <= -velocityThreshold) {
- false
- } else {
- velocity >= velocityThreshold ||
- (offset >= positionalThreshold && !wasCommitted) ||
- isCloserToTarget()
- }
-}
-
-private fun CoroutineScope.animateOffset(
- transition: SwipeTransition,
- layoutImpl: SceneTransitionLayoutImpl,
- initialVelocity: Float,
- targetOffset: Float,
- targetScene: SceneKey,
-) {
- transition.startOffsetAnimation {
- launch {
- if (!transition.isAnimatingOffset) {
- transition.offsetAnimatable.snapTo(transition.dragOffset)
+ private fun animateOffset(
+ initialVelocity: Float,
+ targetOffset: Float,
+ targetScene: SceneKey,
+ ) {
+ swipeTransition.startOffsetAnimation {
+ coroutineScope.launch {
+ if (!isAnimatingOffset) {
+ swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
}
- transition.isAnimatingOffset = true
+ isAnimatingOffset = true
- transition.offsetAnimatable.animateTo(
+ swipeTransition.offsetAnimatable.animateTo(
targetOffset,
// TODO(b/290184746): Make this spring spec configurable.
spring(
@@ -471,64 +434,229 @@
initialVelocity = initialVelocity,
)
+ isAnimatingOffset = false
+
// Now that the animation is done, the state should be idle. Note that if the state
// was changed since this animation started, some external code changed it and we
// shouldn't do anything here. Note also that this job will be cancelled in the case
// where the user intercepts this swipe.
- if (layoutImpl.state.transitionState == transition) {
- layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+ if (isDrivingTransition) {
+ transitionState = TransitionState.Idle(targetScene)
}
}
- .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } }
+ }
+ }
+
+ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+ var _currentScene by mutableStateOf(initialScene)
+ override val currentScene: SceneKey
+ get() = _currentScene.key
+
+ var _fromScene by mutableStateOf(initialScene)
+ override val fromScene: SceneKey
+ get() = _fromScene.key
+
+ var _toScene by mutableStateOf(initialScene)
+ override val toScene: SceneKey
+ get() = _toScene.key
+
+ override val progress: Float
+ get() {
+ val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ if (distance == 0f) {
+ // This can happen only if fromScene == toScene.
+ error(
+ "Transition.progress should be called only when Transition.fromScene != " +
+ "Transition.toScene"
+ )
+ }
+ return offset / distance
+ }
+
+ override val isUserInputDriven = true
+
+ /** The current offset caused by the drag gesture. */
+ var dragOffset by mutableFloatStateOf(0f)
+
+ /**
+ * Whether the offset is animated (the user lifted their finger) or if it is driven by
+ * gesture.
+ */
+ var isAnimatingOffset by mutableStateOf(false)
+
+ /** The animatable used to animate the offset once the user lifted its finger. */
+ val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+ /** Job to check that there is at most one offset animation in progress. */
+ private var offsetAnimationJob: Job? = null
+
+ /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+ fun startOffsetAnimation(job: () -> Job) {
+ stopOffsetAnimation()
+ offsetAnimationJob = job()
+ }
+
+ /** Stops any ongoing offset animation. */
+ fun stopOffsetAnimation() {
+ offsetAnimationJob?.cancel()
+
+ if (isAnimatingOffset) {
+ isAnimatingOffset = false
+ dragOffset = offsetAnimatable.value
+ }
+ }
+
+ /** The absolute distance between [fromScene] and [toScene]. */
+ var absoluteDistance = 0f
+
+ /**
+ * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+ * above or to the left of [toScene].
+ */
+ var _distance by mutableFloatStateOf(0f)
+ val distance: Float
+ get() = _distance
+ }
+
+ companion object {
+ private const val TAG = "SceneGestureHandler"
}
}
-private fun CoroutineScope.animateOverscroll(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- velocity: Velocity,
- orientation: Orientation,
-): Velocity {
- val velocityAmount =
- when (orientation) {
- Orientation.Vertical -> velocity.y
- Orientation.Horizontal -> velocity.x
+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) {
+ if (gestureHandler.gestureWithPriority == this) {
+ gestureHandler.onDrag(delta = pixels)
+ }
+ }
+
+ override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
+ if (gestureHandler.gestureWithPriority == this) {
+ gestureHandler.gestureWithPriority = null
+ gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
+ }
+ }
+}
+
+@VisibleForTesting
+class SceneNestedScrollHandler(
+ private val gestureHandler: SceneGestureHandler,
+) : NestedScrollHandler {
+ override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
+
+ private fun Offset.toAmount() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
}
- if (velocityAmount == 0f) {
- // There is no remaining velocity
- return Velocity.Zero
+ private fun Velocity.toAmount() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
+
+ private fun Float.toOffset() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> Offset(x = this, y = 0f)
+ Orientation.Vertical -> Offset(x = 0f, y = this)
+ }
+
+ private fun nestedScrollConnection(): PriorityNestedScrollConnection {
+ // The next potential scene is calculated during the canStart
+ var nextScene: SceneKey? = null
+
+ // This is the scene on which we will have priority during the scroll gesture.
+ var priorityScene: SceneKey? = null
+
+ // If we performed a long gesture before entering priority mode, we would have to avoid
+ // moving on to the next scene.
+ var gestureStartedOnNestedChild = false
+
+ fun findNextScene(amount: Float): SceneKey? {
+ val fromScene = gestureHandler.currentScene
+ return when {
+ amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation)
+ amount > 0f -> fromScene.downOrRight(gestureHandler.orientation)
+ else -> null
+ }
+ }
+
+ return PriorityNestedScrollConnection(
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+ val canInterceptPreScroll =
+ gestureHandler.isDrivingTransition &&
+ !gestureStartedOnNestedChild &&
+ offsetAvailable.toAmount() != 0f
+
+ if (!canInterceptPreScroll) return@PriorityNestedScrollConnection false
+
+ nextScene = gestureHandler.swipeTransitionToScene.key
+
+ true
+ },
+ canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
+ val amount = offsetAvailable.toAmount()
+ if (amount == 0f) return@PriorityNestedScrollConnection false
+
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+ nextScene = findNextScene(amount)
+ nextScene != null
+ },
+ canStartPostFling = { velocityAvailable ->
+ val amount = velocityAvailable.toAmount()
+ if (amount == 0f) return@PriorityNestedScrollConnection false
+
+ // We could start an overscroll animation
+ gestureStartedOnNestedChild = true
+ nextScene = findNextScene(amount)
+ nextScene != null
+ },
+ 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
+ // initiated in a nested child.
+ gestureHandler.onDrag(amount)
+
+ amount.toOffset()
+ },
+ onStop = { velocityAvailable ->
+ if (gestureHandler.gestureWithPriority != this) {
+ return@PriorityNestedScrollConnection Velocity.Zero
+ }
+
+ priorityScene = null
+
+ gestureHandler.onDragStopped(
+ velocity = velocityAvailable.toAmount(),
+ canChangeScene = !gestureStartedOnNestedChild
+ )
+
+ // The onDragStopped animation consumes any remaining velocity.
+ velocityAvailable
+ },
+ )
}
-
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
- val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
-
- if (!isValidTarget || layoutImpl.state.transitionState == transition) {
- // We have not found a valid target or we are already in a transition
- return Velocity.Zero
- }
-
- transition._currentScene = fromScene
- transition._fromScene = fromScene
- transition._toScene = layoutImpl.scene(target.sceneKey)
- transition._distance = target.distance
- transition.absoluteDistance = target.distance.absoluteValue
- transition.stopOffsetAnimation()
- transition.dragOffset = 0f
-
- layoutImpl.state.transitionState = transition
-
- animateOffset(
- transition = transition,
- layoutImpl = layoutImpl,
- initialVelocity = velocityAmount,
- targetOffset = 0f,
- targetScene = fromScene.key
- )
-
- // The animateOffset animation consumes any remaining velocity.
- return velocity
}
/**
@@ -536,127 +664,3 @@
* which the animation can stop.
*/
private const val OffsetVisibilityThreshold = 0.5f
-
-@Composable
-private fun rememberSwipeToSceneNestedScrollConnection(
- orientation: Orientation,
- coroutineScope: CoroutineScope,
- draggableState: DraggableState,
- transition: SwipeTransition,
- layoutImpl: SceneTransitionLayoutImpl,
- velocityThreshold: Float,
- positionalThreshold: Float,
-): PriorityPostNestedScrollConnection {
- val density = LocalDensity.current
- val scrollConnection =
- remember(
- orientation,
- coroutineScope,
- draggableState,
- transition,
- layoutImpl,
- velocityThreshold,
- positionalThreshold,
- density,
- ) {
- fun Offset.toAmount() =
- when (orientation) {
- Orientation.Horizontal -> x
- Orientation.Vertical -> y
- }
-
- fun Velocity.toAmount() =
- when (orientation) {
- Orientation.Horizontal -> x
- Orientation.Vertical -> y
- }
-
- fun Float.toOffset() =
- when (orientation) {
- Orientation.Horizontal -> Offset(x = this, y = 0f)
- Orientation.Vertical -> Offset(x = 0f, y = this)
- }
-
- // The next potential scene is calculated during the canStart
- var nextScene: SceneKey? = null
-
- // This is the scene on which we will have priority during the scroll gesture.
- var priorityScene: SceneKey? = null
-
- // If we performed a long gesture before entering priority mode, we would have to avoid
- // moving on to the next scene.
- var gestureStartedOnNestedChild = false
-
- PriorityPostNestedScrollConnection(
- canStart = { offsetAvailable, offsetBeforeStart ->
- val amount = offsetAvailable.toAmount()
- if (amount == 0f) return@PriorityPostNestedScrollConnection false
-
- gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
-
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- nextScene =
- when {
- amount < 0f -> fromScene.upOrLeft(orientation)
- amount > 0f -> fromScene.downOrRight(orientation)
- else -> null
- }
-
- nextScene != null
- },
- canContinueScroll = { priorityScene == transition._toScene.key },
- onStart = {
- priorityScene = nextScene
- onDragStarted(layoutImpl, transition, orientation)
- },
- onScroll = { offsetAvailable ->
- val amount = offsetAvailable.toAmount()
-
- // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
- // is initiated in a nested child.
-
- // Appends a new coroutine to attempt to drag by [amount] px. In this case we
- // are assuming that the [coroutineScope] is tied to the main thread and that
- // calls to [launch] are therefore queued.
- coroutineScope.launch { draggableState.drag { dragBy(amount) } }
-
- amount.toOffset()
- },
- onStop = { velocityAvailable ->
- priorityScene = null
-
- coroutineScope.onDragStopped(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocityAvailable.toAmount(),
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold,
- canChangeScene = !gestureStartedOnNestedChild
- )
-
- // The onDragStopped animation consumes any remaining velocity.
- velocityAvailable
- },
- onPostFling = { velocityAvailable ->
- // If there is any velocity left, we can try running an overscroll animation
- // between scenes.
- coroutineScope.animateOverscroll(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocityAvailable,
- orientation = orientation
- )
- },
- )
- }
- DisposableEffect(scrollConnection) {
- onDispose {
- coroutineScope.launch {
- // This should ensure that the draggableState is in a consistent state and that it
- // does not cause any unexpected behavior.
- scrollConnection.reset()
- }
- }
- }
- return scrollConnection
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
similarity index 74%
rename from packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 793a9a5..824c10b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,22 +22,23 @@
import androidx.compose.ui.unit.Velocity
/**
- * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
- * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
- * until [canContinueScroll] allows it.
+ * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
+ * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling.
+ * If it does, it will scroll before its children, until [canContinueScroll] allows it.
*
* Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
* after [onStart].
*
* @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
*/
-class PriorityPostNestedScrollConnection(
- private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+class PriorityNestedScrollConnection(
+ private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
private val canContinueScroll: () -> Boolean,
private val onStart: () -> Unit,
private val onScroll: (offsetAvailable: Offset) -> Offset,
private val onStop: (velocityAvailable: Velocity) -> Velocity,
- private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
) : NestedScrollConnection {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
@@ -57,26 +58,21 @@
if (
isPriorityMode ||
source == NestedScrollSource.Fling ||
- !canStart(available, offsetBeforeStart)
+ !canStartPostScroll(available, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- // Step 1: It's our turn! We start capturing scroll events when one of our children has an
- // available offset following a scroll event.
- isPriorityMode = true
-
- // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
- // lifted (step 3b), or this object has been destroyed (step 3c).
- onStart()
-
- return onScroll(available)
+ return onPriorityStart(available)
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
if (source != NestedScrollSource.Fling) {
+ if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(available)
+ }
// We want to track the amount of offset consumed before entering priority mode
offsetScrolledBeforePriorityMode += available
}
@@ -87,6 +83,11 @@
if (!canContinueScroll()) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
onPriorityStop(velocity = Velocity.Zero)
+
+ // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+ // We want to track the amount of offset consumed before entering priority mode
+ offsetScrolledBeforePriorityMode += available
+
return Offset.Zero
}
@@ -101,7 +102,14 @@
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- return onPostFling(available)
+ if (!canStartPostFling(available)) {
+ return Velocity.Zero
+ }
+
+ onPriorityStart(available = Offset.Zero)
+
+ // This is the last event of a scroll gesture.
+ return onPriorityStop(available)
}
/** Method to call before destroying the object or to reset the initial state. */
@@ -110,8 +118,23 @@
onPriorityStop(velocity = Velocity.Zero)
}
- private fun onPriorityStop(velocity: Velocity): Velocity {
+ private fun onPriorityStart(available: Offset): Offset {
+ if (isPriorityMode) {
+ error("This should never happen, onPriorityStart() was called when isPriorityMode")
+ }
+ // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+ // available offset following a scroll event.
+ isPriorityMode = true
+
+ // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+ // lifted (step 3b), or this object has been destroyed (step 3c).
+ onStart()
+
+ return onScroll(available)
+ }
+
+ private fun onPriorityStop(velocity: Velocity): Velocity {
// We can restart tracking the consumed offsets from scratch.
offsetScrolledBeforePriorityMode = Offset.Zero
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
new file mode 100644
index 0000000..6791a85
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -0,0 +1,363 @@
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TransitionState.Idle
+import com.android.compose.animation.scene.TransitionState.Transition
+import com.android.compose.test.MonotonicClockTestScope
+import com.android.compose.test.runMonotonicClockTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val SCREEN_SIZE = 100f
+
+@RunWith(AndroidJUnit4::class)
+class SceneGestureHandlerTest {
+ private class TestGestureScope(
+ val coroutineScope: MonotonicClockTestScope,
+ ) {
+ private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
+
+ private val layoutState: SceneTransitionLayoutState =
+ SceneTransitionLayoutState(internalCurrentScene)
+
+ private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
+ scene(
+ key = SceneA,
+ userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+ ) {
+ Text("SceneA")
+ }
+ scene(SceneB) { Text("SceneB") }
+ scene(SceneC) { Text("SceneC") }
+ }
+
+ val sceneGestureHandler =
+ SceneGestureHandler(
+ layoutImpl =
+ SceneTransitionLayoutImpl(
+ onChangeScene = { internalCurrentScene = it },
+ builder = scenesBuilder,
+ transitions = EmptyTestTransitions,
+ state = layoutState,
+ density = Density(1f)
+ )
+ .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) },
+ orientation = Orientation.Vertical,
+ coroutineScope = coroutineScope,
+ )
+
+ val draggable = sceneGestureHandler.draggable
+
+ val nestedScroll = sceneGestureHandler.nestedScroll.connection
+
+ val velocityThreshold = sceneGestureHandler.velocityThreshold
+
+ // 10% of the screen
+ val deltaInPixels10 = SCREEN_SIZE * 0.1f
+
+ // Offset y: 10% of the screen
+ val offsetY10 = Offset(x = 0f, y = deltaInPixels10)
+
+ val transitionState: TransitionState
+ get() = layoutState.transitionState
+
+ fun advanceUntilIdle() {
+ coroutineScope.testScheduler.advanceUntilIdle()
+ }
+
+ fun runCurrent() {
+ coroutineScope.testScheduler.runCurrent()
+ }
+
+ fun assertScene(currentScene: SceneKey, isIdle: Boolean) {
+ val idleMsg = if (isIdle) "MUST" else "MUST NOT"
+ assertWithMessage("transitionState $idleMsg be Idle")
+ .that(transitionState is Idle)
+ .isEqualTo(isIdle)
+ assertThat(transitionState.currentScene).isEqualTo(currentScene)
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
+ runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
+ }
+
+ @Test
+ fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) }
+
+ @Test
+ fun onDragStarted_shouldStartATransition() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+ }
+
+ @Test
+ fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+ val transition = transitionState as Transition
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertThat(transition.progress).isEqualTo(0.1f)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertThat(transition.progress).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold - 0.01f,
+ )
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold,
+ )
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneC, isIdle = true)
+ }
+
+ @Test
+ fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold,
+ )
+
+ // The stop animation is not started yet
+ assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+
+ runCurrent()
+
+ assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // Start a new gesture while the offset is animating
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+ }
+
+ @Test
+ fun onInitialPreScroll_doNotChangeState() = runGestureTest {
+ nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onPostScrollWithNothingAvailable_doNotChangeState() = runGestureTest {
+ val consumed =
+ nestedScroll.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+
+ assertScene(currentScene = SceneA, isIdle = true)
+ assertThat(consumed).isEqualTo(Offset.Zero)
+ }
+
+ @Test
+ fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
+ val consumed =
+ nestedScroll.onPostScroll(
+ consumed = Offset.Zero,
+ available = offsetY10,
+ source = NestedScrollSource.Drag
+ )
+
+ assertScene(currentScene = SceneA, isIdle = false)
+ val transition = transitionState as Transition
+ assertThat(transition.progress).isEqualTo(0.1f)
+ assertThat(consumed).isEqualTo(offsetY10)
+ }
+
+ private fun TestGestureScope.nestedScrollEvents(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ nestedScroll.onPreScroll(available = available, source = NestedScrollSource.Drag)
+ val consumed = consumedByPreScroll + consumedByScroll
+ nestedScroll.onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ @Test
+ fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ val transition = transitionState as Transition
+ assertThat(transition.progress).isEqualTo(0.1f)
+
+ // start intercept preScroll
+ val consumed =
+ nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ assertThat(transition.progress).isEqualTo(0.2f)
+
+ // do nothing on postScroll
+ nestedScroll.onPostScroll(
+ consumed = consumed,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(transition.progress).isEqualTo(0.2f)
+
+ nestedScrollEvents(available = offsetY10)
+ assertThat(transition.progress).isEqualTo(0.3f)
+ assertScene(currentScene = SceneA, isIdle = false)
+ }
+
+ @Test
+ fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onPreFling_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneC, isIdle = true)
+ }
+
+ @Test
+ fun scrollStartedInScene_doOverscrollAnimation() = runGestureTest {
+ // we started the scroll in the scene
+ nestedScrollEvents(available = offsetY10, consumedByScroll = offsetY10)
+
+ // now we can intercept the scroll events
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ // should start an overscroll animation (the gesture started in the scene)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ 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/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
similarity index 66%
rename from packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 8e2b77a..122774b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -29,20 +29,22 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class PriorityPostNestedScrollConnectionTest {
- private var canStart = false
+class PriorityNestedScrollConnectionTest {
+ private var canStartPreScroll = false
+ private var canStartPostScroll = false
+ private var canStartPostFling = false
private var canContinueScroll = false
private var isStarted = false
private var lastScroll: Offset? = null
private var returnOnScroll = Offset.Zero
private var lastStop: Velocity? = null
private var returnOnStop = Velocity.Zero
- private var lastOnPostFling: Velocity? = null
- private var returnOnPostFling = Velocity.Zero
private val scrollConnection =
- PriorityPostNestedScrollConnection(
- canStart = { _, _ -> canStart },
+ PriorityNestedScrollConnection(
+ canStartPreScroll = { _, _ -> canStartPreScroll },
+ canStartPostScroll = { _, _ -> canStartPostScroll },
+ canStartPostFling = { canStartPostFling },
canContinueScroll = { canContinueScroll },
onStart = { isStarted = true },
onScroll = {
@@ -53,10 +55,6 @@
lastStop = it
returnOnStop
},
- onPostFling = {
- lastOnPostFling = it
- returnOnPostFling
- },
)
private val offset1 = Offset(1f, 1f)
@@ -64,8 +62,29 @@
private val velocity1 = Velocity(1f, 1f)
private val velocity2 = Velocity(2f, 2f)
- private fun startPriorityMode() {
- canStart = true
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
+ canStartPreScroll = true
+
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ private fun startPriorityModePostScroll() {
+ canStartPostScroll = true
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
@@ -75,7 +94,7 @@
@Test
fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
- canStart = true
+ canStartPostScroll = true
scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
assertThat(isStarted).isEqualTo(false)
@@ -86,7 +105,7 @@
scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
assertThat(isStarted).isEqualTo(false)
- startPriorityMode()
+ startPriorityModePostScroll()
assertThat(isStarted).isEqualTo(true)
}
@@ -99,13 +118,13 @@
)
assertThat(isStarted).isEqualTo(false)
- startPriorityMode()
+ startPriorityModePostScroll()
assertThat(isStarted).isEqualTo(true)
}
@Test
fun step1_onPriorityModeStarted_receiveAvailableOffset() {
- canStart = true
+ canStartPostScroll = true
scrollConnection.onPostScroll(
consumed = offset1,
@@ -118,7 +137,7 @@
@Test
fun step2_onPriorityMode_shouldContinueIfAllowed() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
@@ -132,7 +151,7 @@
@Test
fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = false
scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
@@ -142,7 +161,7 @@
@Test
fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.onPreFling(available = Velocity.Zero)
@@ -152,7 +171,7 @@
@Test
fun step3c_onPriorityMode_shouldStopOnReset() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.reset()
@@ -162,11 +181,34 @@
@Test
fun receive_onPostFling() = runTest {
+ canStartPostFling = true
+
scrollConnection.onPostFling(
consumed = velocity1,
available = velocity2,
)
- assertThat(lastOnPostFling).isEqualTo(velocity2)
+ assertThat(lastStop).isEqualTo(velocity2)
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
+ canStartPostFling = true
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(true)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
new file mode 100644
index 0000000..cb122dc
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -0,0 +1,27 @@
+package com.android.compose.test
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.TestMonotonicFrameClock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+
+/**
+ * This method creates a [CoroutineScope] that can be used in animations created in a composable
+ * function.
+ *
+ * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ */
+@ExperimentalTestApi
+fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
+ // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
+ withContext(TestMonotonicFrameClock(this)) {
+ MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ }
+}
+
+class MonotonicClockTestScope(
+ coroutineScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler
+) : CoroutineScope by coroutineScope
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
new file mode 100755
index 0000000..5db27d8
--- /dev/null
+++ b/packages/SystemUI/flag_check.py
@@ -0,0 +1,134 @@
+#! /usr/bin/env python3
+
+import sys
+import re
+import argparse
+
+# partially copied from tools/repohooks/rh/hooks.py
+
+TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the
+following case-sensitive regex:
+
+ %s
+
+The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
+
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
+flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+
+Some examples below:
+
+Flag: NONE
+Flag: NA
+Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+
+Check the git history for more examples. It's a regex matched field.
+"""
+
+def main():
+ """Check the commit message for a 'Flag:' line."""
+ parser = argparse.ArgumentParser(
+ description='Check the commit message for a Flag: line.')
+ parser.add_argument('--msg',
+ metavar='msg',
+ type=str,
+ nargs='?',
+ default='HEAD',
+ help='commit message to process.')
+ parser.add_argument(
+ '--files',
+ metavar='files',
+ nargs='?',
+ default='',
+ help=
+ 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
+ parser.add_argument(
+ '--project',
+ metavar='project',
+ type=str,
+ nargs='?',
+ default='',
+ help=
+ 'REPO_PATH in repo upload to determine whether the check should run for this project.')
+
+ # Parse the arguments
+ args = parser.parse_args()
+ desc = args.msg
+ files = args.files
+ project = args.project
+
+ if not should_run_path(project, files):
+ return
+
+ field = 'Flag'
+ none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+
+ typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
+
+ # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
+ # Aconfig Flag name format = "packageName"."flagName"
+ # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
+ # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+ #common_typos_disable
+ flagName = '([a-zA-z0-9_.])+'
+
+ #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
+ stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+ #common_typos_enable
+
+ readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD'
+
+ flagRegex = fr'^{field}: .*$'
+ check_flag = re.compile(flagRegex) #Flag:
+
+ # Ignore case for flag name format.
+ flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+ check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
+
+ flagError = False
+ foundFlag = []
+ # Check for multiple "Flag:" lines and all lines should match this format
+ for line in desc.splitlines():
+ if check_flag.match(line):
+ if not check_flagName.match(line):
+ flagError = True
+ break
+ foundFlag.append(line)
+
+ # Throw error if
+ # 1. No "Flag:" line is found
+ # 2. "Flag:" doesn't follow right format.
+ if (not foundFlag) or (flagError):
+ error = TEST_MSG % (readableRegexMsg)
+ print(error)
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+def should_run_path(path, files):
+ """Returns a boolean if this check should run with these paths.
+ If you want to check for a particular subdirectory under the path,
+ add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
+ """
+ if not path:
+ return False
+ if path == 'frameworks/base':
+ return should_run_files(files)
+ # Default case, run for all other paths which calls this script.
+ return True
+
+
+def should_run_files(files):
+ """Returns a boolean if this check should run with these files."""
+ if not files:
+ return False
+ if 'packages/SystemUI' in files:
+ return True
+ return False
+
+
+if __name__ == '__main__':
+ main()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
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
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 07ffd11..1620088 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 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 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.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 @@
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 @@
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/multivalentTestsForDevice b/packages/SystemUI/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTestsForDeviceless b/packages/SystemUI/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
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 9cc87fd..f0e3c99 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 @@
@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/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a9d2ee3..403c7c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -19,7 +19,6 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;
-import android.widget.ImageView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.plugins.annotations.DependsOn;
@@ -51,21 +50,11 @@
void addDarkReceiver(DarkReceiver receiver);
/**
- * Adds a receiver to receive callbacks onDarkChanged
- */
- void addDarkReceiver(ImageView imageView);
-
- /**
* Must have been previously been added through one of the addDarkReceive methods above.
*/
void removeDarkReceiver(DarkReceiver object);
/**
- * Must have been previously been added through one of the addDarkReceive methods above.
- */
- void removeDarkReceiver(ImageView object);
-
- /**
* Used to reapply darkness on an object, must have previously been added through
* addDarkReceiver.
*/
@@ -104,8 +93,8 @@
}
/**
- * @return true if more than half of the view area are in any of the given
- * areas, false otherwise
+ * @return true if more than half of the view's area is in any of the given area Rects, false
+ * otherwise
*/
static boolean isInAreas(Collection<Rect> areas, View view) {
if (areas.isEmpty()) {
@@ -120,9 +109,40 @@
}
/**
- * @return true if more than half of the view area are in area, false
+ * @return true if more than half of the viewBounds are in any of the given area Rects, false
* otherwise
*/
+ static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) {
+ if (areas.isEmpty()) {
+ return true;
+ }
+ for (Rect area : areas) {
+ if (isInArea(area, viewBounds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */
+ static boolean isInArea(Rect area, Rect viewBounds) {
+ if (area.isEmpty()) {
+ return true;
+ }
+ sTmpRect.set(area);
+ int left = viewBounds.left;
+ int width = viewBounds.width();
+
+ int intersectStart = Math.max(left, area.left);
+ int intersectEnd = Math.min(left + width, area.right);
+ int intersectAmount = Math.max(0, intersectEnd - intersectStart);
+
+ boolean coversFullStatusBar = area.top <= 0;
+ boolean majorityOfWidth = 2 * intersectAmount > width;
+ return majorityOfWidth && coversFullStatusBar;
+ }
+
+ /** @return true if more than half of the view's area is in the area Rect, false otherwise */
static boolean isInArea(Rect area, View view) {
if (area.isEmpty()) {
return true;
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index 2eb1bb5..a6a8122 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 5fd946b..fb84414 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 b6479f4..fb33092 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 a41a704..a123bb7 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 ed969c7..b133b30a 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 b0a6471..9a91962 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 11cc77d..5e46b715 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 c554a27..ab931ed 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 67b4e4b..e25de93 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 4c519c8..cd7aaeb 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 3bd6508..bf8a592 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 573638b..bedafd8 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 c7c863b..93f505e 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 5c5f264..01e166e 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 3a01da5..9769242 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 0ace8a7..087ab3a 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 480bcbb..7297cf9 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 0ace8a7..087ab3a 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 0ace8a7..087ab3a 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index b8e89f4..ead8bce 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 debbeb1..5b82c44 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 0ea98a8..cf7f3d2 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 722a022..6335ca8 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 d329369..b47c58a 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 ae3f04a..f274f5f 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 050df99..dd9ce2e4 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 fa1a191..742f56e 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 d687a1d..92d24c4 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 3faa7ca..4837de2 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Debuxa o padrón para instalar a actualización máis tarde"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Actualizouse o dispositivo. Mete o PIN para continuar."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Actualizouse o dispositivo. Mete o contrasinal para continuar."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón para continuar."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 99c9883..7f8c6d8 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 9d32f04..18d63ab 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 b4224bf..0206faf 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 bc712c7..8575e10 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 4d7bbbe..a7c3aba 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 aa766e9..f9a840f 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 99f1779..b7147c2 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 cc0a164..9e1b187 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 bc66355..16316ce 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"צריך למתוח את קו ביטול הנעילה כדי להתקין את העדכון מאוחר יותר"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"המכשיר עודכן. צריך להזין את קוד האימות כדי להמשיך."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"המכשיר עודכן. צריך להזין את הסיסמה כדי להמשיך."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. צריך למתוח את קו ביטול הנעילה כדי להמשיך."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. יש למתוח קו ביטול נעילה כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 1d59a63..6e8f423 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 5bd6b2e..a31243d 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 83d270d..6a77783 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 5306cb1..cda9520 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 d609a23..e24005a 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 532253e..7378cc78 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>
@@ -129,6 +125,6 @@
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"나중에 업데이트를 설치하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"나중에 업데이트를 설치하려면 패턴을 그리세요."</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"기기가 업데이트되었습니다. 계속하려면 PIN을 입력하세요."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속 진행하려면 비밀번호를 입력하세요."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"기기가 업데이트되었습니다. 계속하려면 패턴을 그리세요."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 1e03c03..88f0b97 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 0059d7f..00a382a 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 01e2f88..31c4107 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 2133694..ecf2233 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 2771c7f..3f089b9 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 02ee66f..be1ea89 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 2b9f81e..54fdecd 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 7aa7bdd..eff4c7a 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 bdfa4a7..d9eb4ca 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 e85cf8a..afbce26 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>
@@ -128,7 +124,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပင်နံပါတ်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် စကားဝှက်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပုံဖော်ရေးဆွဲပါ"</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 455d086..3098e87 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 f0094a3..45b8819 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 a236639..af24d40 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Teken het patroon om de update later te installeren"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Apparaat geüpdatet. Voer de pincode in om door te gaan."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Apparaat geüpdatet. Voer het wachtwoord in om door te gaan."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken het patroon om door te gaan."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken patroon om door te gaan."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index b31c9c0..8cae987 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 209b63f..18959c8 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 7ec988e..bd00ba9 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 78a8091..54e270f 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 5549b36..2e37bde 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 78a8091..54e270f 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 4309b56..ead09209 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>
@@ -130,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitiv actualizat. Desenează modelul și continuă."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 45149a5..595fba5 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>
@@ -128,7 +124,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Введите PIN-код, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Введите пароль, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Введите графический ключ, чтобы установить обновление позже."</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Введите PIN-код."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Введите пароль."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Введите графический ключ."</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Чтобы продолжить, введите PIN-код."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Чтобы продолжить, введите пароль."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Чтобы продолжить, введите графический ключ."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 17ced75..b6a7422 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 ef08a6c..5e34a94 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 a42989c..3508f3b 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 ce53b7e..8d71b0f 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 437018d..4093952 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 b4b1996..5b01f39 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 8ca9046..72f1fc3 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 7671194..20eb8ef 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 623b589..d496944 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 c244107..605d077 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 cd8f810..040ec9e 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 ddeba67..750ba11 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 f06d17d..169ea1f 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 8adbaca..d7f7b65 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 96dfa05..40dbaf3 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 41b5a33..d5a33d3 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 4c65832..6de9ff9 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 dad6f31..11966ca 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 88b7e43..e4f868a 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 c5e99ab..4fadc2e 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 28b5870..565ed10 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/color/notification_guts_priority_button_bg_stroke.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
index 015e9f9..d1b8a06 100644
--- a/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
@@ -14,8 +14,9 @@
~ limitations under the License
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:state_selected="true"
- android:color="?android:attr/colorAccent" />
+ android:color="?androidprv:attr/materialColorOnSurfaceVariant" />
<item android:color="@color/notification_guts_priority_button_bg_stroke_color" />
</selector>
diff --git a/packages/SystemUI/res/color/notification_guts_priority_contents.xml b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
index 42f0189..cc8c25a 100644
--- a/packages/SystemUI/res/color/notification_guts_priority_contents.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
@@ -14,8 +14,9 @@
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:state_selected="true"
- android:color="?android:attr/colorAccent" />
+ android:color="?androidprv:attr/materialColorOnSurfaceVariant" />
<item android:color="@color/notification_guts_priority_button_content_color" />
</selector>
diff --git a/packages/SystemUI/res/color/remote_input_hint.xml b/packages/SystemUI/res/color/remote_input_hint.xml
index 7fe58db..0d90ee6 100644
--- a/packages/SystemUI/res/color/remote_input_hint.xml
+++ b/packages/SystemUI/res/color/remote_input_hint.xml
@@ -14,6 +14,7 @@
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="?android:attr/textColorPrimary" android:alpha=".6" />
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/materialColorOnSurfaceVariant" android:alpha=".6" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/remote_input_send.xml b/packages/SystemUI/res/color/remote_input_send.xml
index 4dcd3dd..0acc66b 100644
--- a/packages/SystemUI/res/color/remote_input_send.xml
+++ b/packages/SystemUI/res/color/remote_input_send.xml
@@ -15,7 +15,8 @@
~ limitations under the License
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:color="?android:attr/colorAccent" android:alpha=".3" />
- <item android:color="?android:attr/colorAccent" />
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_enabled="false" android:color="?androidprv:attr/materialColorPrimary" android:alpha=".3" />
+ <item android:color="?androidprv:attr/materialColorPrimary" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/remote_input_text.xml b/packages/SystemUI/res/color/remote_input_text.xml
index 13bb1d7..bf2c198 100644
--- a/packages/SystemUI/res/color/remote_input_text.xml
+++ b/packages/SystemUI/res/color/remote_input_text.xml
@@ -15,7 +15,8 @@
~ limitations under the License
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:color="?android:attr/textColorPrimary" android:alpha=".6" />
- <item android:color="?android:attr/textColorPrimary" />
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_enabled="false" android:color="?androidprv:attr/materialColorOnSurfaceVariant" android:alpha=".6" />
+ <item android:color="?androidprv:attr/materialColorOnSurfaceVariant" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index e626675..b959737 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -26,7 +26,7 @@
<padding
android:left="20dp"
android:right="20dp" />
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
</shape>
</inset>
</item>
diff --git a/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml
index bd9394b..84e2231 100644
--- a/packages/SystemUI/res/drawable/notification_guts_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_guts_bg.xml
@@ -17,7 +17,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
<!--The radius is 1dp smaller than the notification one, to avoid aliasing bugs on the corners -->
<corners android:radius="1dp" />
</shape>
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 3eaa618..355e75d 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -20,7 +20,7 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape>
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
</shape>
</item>
<item>
diff --git a/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml b/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml
index 535b354..45d1a53 100644
--- a/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml
+++ b/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml
@@ -14,12 +14,13 @@
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?android:attr/colorBackgroundFloating" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceDim" />
<stroke
android:width="@dimen/remote_input_view_text_stroke"
- android:color="?android:attr/colorAccent"/>
+ android:color="?androidprv:attr/materialColorPrimary"/>
<padding
android:bottom="0dp"
android:left="12dp"
diff --git a/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml
index 8b34080..06bed00 100644
--- a/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml
+++ b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml
@@ -15,11 +15,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
- android:fillColor="@color/notification_section_clear_all_btn_color"
+ android:fillColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:pathData="M24.6667 16.2733L23.7267 15.3333L20 19.06L16.2734 15.3333L15.3334 16.2733L19.06 20L15.3334 23.7267L16.2734 24.6667L20 20.94L23.7267 24.6667L24.6667 23.7267L20.94 20L24.6667 16.2733Z"/>
</vector>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index bddcb6a..cf301c9 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -79,7 +79,4 @@
android:tint="@color/accent_tint_color_selector"
android:soundEffectsEnabled="false" />
</LinearLayout>
-
- <include layout="@layout/volume_dnd_icon"/>
-
</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index 3b70dc0..5ce2601 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -70,12 +70,6 @@
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
-
- <include layout="@layout/volume_dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_dialog_stream_padding"
- android:layout_marginTop="6dp"/>
</FrameLayout>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 6d77943..08eccbb 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -44,10 +44,9 @@
android:paddingTop="10dp"
android:maxLines="1"
android:ellipsize="end"
- app:layout_constraintWidth_percent="0.7"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
- app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+ app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary"
android:gravity="center_vertical"
android:textSize="14sp" />
@@ -60,34 +59,35 @@
android:paddingBottom="10dp"
android:maxLines="1"
android:ellipsize="end"
- app:layout_constraintWidth_percent="0.7"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
- app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+ app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center_vertical" />
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/guideline"
+ app:layout_constraintGuide_percent="0.8"
+ android:orientation="vertical"/>
+
<View
android:id="@+id/gear_icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
- app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
- app:layout_constraintEnd_toEndOf="@+id/gear_icon_image"
+ app:layout_constraintStart_toEndOf="@+id/guideline"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageView
android:id="@+id/gear_icon_image"
- android:src="@drawable/ic_settings_24dp"
- android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
android:layout_width="0dp"
android:layout_height="24dp"
- app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ android:src="@drawable/ic_settings_24dp"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintWidth_percent="0.3"
- android:gravity="center_vertical"
- android:paddingStart="10dp" />
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 5d986e0..c11a18b 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -51,147 +51,160 @@
android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toTopOf="@id/bluetooth_toggle_title"
+ app:layout_constraintBottom_toTopOf="@+id/scroll_view"
app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
- <TextView
- android:id="@+id/bluetooth_toggle_title"
- style="@style/BluetoothTileDialog.Device"
- android:layout_width="0dp"
- android:layout_height="64dp"
- android:maxLines="1"
- android:ellipsize="end"
- android:gravity="center_vertical"
- android:layout_marginTop="4dp"
- android:text="@string/turn_on_bluetooth"
- android:clickable="false"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
- android:textSize="16sp"
- app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
-
- <Switch
- android:id="@+id/bluetooth_toggle"
- style="@style/BluetoothTileDialog.Device"
- android:layout_width="0dp"
- android:layout_height="48dp"
- android:gravity="center|center_vertical"
- android:paddingEnd="24dp"
- android:layout_marginTop="10dp"
- android:contentDescription="@string/turn_on_bluetooth"
- android:switchMinWidth="@dimen/settingslib_switch_track_width"
- android:theme="@style/MainSwitch.Settingslib"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:track="@drawable/settingslib_track_selector"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
- app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/device_list"
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:nestedScrollingEnabled="false"
- android:overScrollMode="never"
- android:scrollbars="vertical"
+ app:layout_constrainedHeight="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle"
- app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
-
- <androidx.constraintlayout.widget.Group
- android:id="@+id/see_all_layout_group"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- app:constraint_referenced_ids="ic_arrow,see_all_text" />
-
- <ImageView
- android:id="@+id/ic_arrow"
- android:layout_marginStart="36dp"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:importantForAccessibility="no"
- android:gravity="center_vertical"
- android:src="@drawable/ic_arrow_forward"
- app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/see_all_text"
- app:layout_constraintTop_toBottomOf="@id/device_list" />
-
- <TextView
- android:id="@+id/see_all_text"
- style="@style/BluetoothTileDialog.Device"
- android:layout_width="0dp"
- android:layout_height="64dp"
- android:maxLines="1"
- android:ellipsize="end"
- android:gravity="center_vertical"
- android:layout_marginStart="0dp"
- android:paddingStart="20dp"
- android:text="@string/see_all_bluetooth_devices"
- android:textSize="14sp"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
- app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
- app:layout_constraintStart_toEndOf="@+id/ic_arrow"
- app:layout_constraintTop_toBottomOf="@id/device_list"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <androidx.constraintlayout.widget.Group
- android:id="@+id/pair_new_device_layout_group"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- app:constraint_referenced_ids="ic_add,pair_new_device_text" />
-
- <ImageView
- android:id="@+id/ic_add"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginStart="36dp"
- android:gravity="center_vertical"
- android:importantForAccessibility="no"
- android:src="@drawable/ic_add"
- app:layout_constraintBottom_toTopOf="@id/done_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
- app:layout_constraintTop_toBottomOf="@id/see_all_text"
- android:tint="?android:attr/textColorPrimary" />
-
- <TextView
- android:id="@+id/pair_new_device_text"
- style="@style/BluetoothTileDialog.Device"
- android:layout_width="0dp"
- android:layout_height="64dp"
- android:maxLines="1"
- android:ellipsize="end"
- android:gravity="center_vertical"
- android:layout_marginStart="0dp"
- android:paddingStart="20dp"
- android:text="@string/pair_new_bluetooth_devices"
- android:textSize="14sp"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
- app:layout_constraintBottom_toTopOf="@id/done_button"
- app:layout_constraintStart_toEndOf="@+id/ic_add"
- app:layout_constraintTop_toBottomOf="@id/see_all_text"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <Button
- android:id="@+id/done_button"
- style="@style/Widget.Dialog.Button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/dialog_bottom_padding"
- android:layout_marginEnd="@dimen/dialog_side_padding"
- android:layout_marginStart="@dimen/dialog_side_padding"
- android:clickable="true"
- android:ellipsize="end"
- android:focusable="true"
- android:maxLines="1"
- android:text="@string/inline_done_button"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" />
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <TextView
+ android:id="@+id/bluetooth_toggle_title"
+ android:layout_width="0dp"
+ android:layout_height="64dp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="0dp"
+ android:paddingStart="36dp"
+ android:text="@string/turn_on_bluetooth"
+ android:clickable="false"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textSize="16sp"
+ app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/device_list"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Switch
+ android:id="@+id/bluetooth_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="40dp"
+ android:contentDescription="@string/turn_on_bluetooth"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:theme="@style/MainSwitch.Settingslib"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:track="@drawable/settingslib_track_selector"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
+ app:layout_constraintBottom_toTopOf="@+id/device_list"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/device_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
+ app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
+
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/see_all_layout_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="ic_arrow,see_all_text" />
+
+ <ImageView
+ android:id="@+id/ic_arrow"
+ android:layout_marginStart="36dp"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:importantForAccessibility="no"
+ android:gravity="center_vertical"
+ android:src="@drawable/ic_arrow_forward"
+ app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/see_all_text"
+ app:layout_constraintTop_toBottomOf="@id/device_list" />
+
+ <TextView
+ android:id="@+id/see_all_text"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
+ android:layout_height="64dp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:layout_marginStart="0dp"
+ android:paddingStart="20dp"
+ android:text="@string/see_all_bluetooth_devices"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
+ app:layout_constraintStart_toEndOf="@+id/ic_arrow"
+ app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/pair_new_device_layout_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="ic_add,pair_new_device_text" />
+
+ <ImageView
+ android:id="@+id/ic_add"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="36dp"
+ android:gravity="center_vertical"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_add"
+ app:layout_constraintBottom_toTopOf="@id/done_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
+ app:layout_constraintTop_toBottomOf="@id/see_all_text"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <TextView
+ android:id="@+id/pair_new_device_text"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
+ android:layout_height="64dp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:layout_marginStart="0dp"
+ android:paddingStart="20dp"
+ android:text="@string/pair_new_bluetooth_devices"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ app:layout_constraintStart_toEndOf="@+id/ic_add"
+ app:layout_constraintTop_toBottomOf="@id/see_all_text"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <Button
+ android:id="@+id/done_button"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/inline_done_button"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+ app:layout_constraintBottom_toBottomOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 8241070..ad9a775 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -115,8 +115,7 @@
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
android:src="@drawable/dream_overlay_assistant_attention_indicator"
android:visibility="gone"
- android:contentDescription=
- "@string/dream_overlay_status_bar_assistant_attention_indicator" />
+ android:contentDescription="@string/assistant_attention_content_description" />
</LinearLayout>
</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index c70f8e2..68c8dd9 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -16,6 +16,7 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/half_shelf_dialog"
android:orientation="vertical"
android:layout_width="wrap_content"
@@ -65,7 +66,7 @@
android:gravity="center_vertical|start"
android:ellipsize="end"
android:maxLines="2"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="16sp"
/>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index 190f999..9ef342c 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -16,6 +16,7 @@
<com.android.systemui.statusbar.notification.row.ChannelRow
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/half_shelf_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -59,7 +60,7 @@
android:ellipsize="end"
android:maxLines="1"
android:fontFamily="@*android:string/config_headlineFontFamily"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:textSize="16sp"
/>
@@ -74,7 +75,7 @@
android:maxLines="1"
android:layout_below="@id/channel_name"
android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:textSize="14sp"
/>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/notification_children_divider.xml b/packages/SystemUI/res/layout/notification_children_divider.xml
index eb74306..13e24a9 100644
--- a/packages/SystemUI/res/layout/notification_children_divider.xml
+++ b/packages/SystemUI/res/layout/notification_children_divider.xml
@@ -17,7 +17,8 @@
<com.android.systemui.statusbar.AlphaOptimizedView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/notification_more_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_divider_height"
- android:background="@color/notification_divider_color" />
+ android:background="?androidprv:attr/materialColorOnSurfaceVariant" />
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 4f6e88c..3a752c8 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -17,6 +17,7 @@
<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/notification_guts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -173,7 +174,7 @@
android:contentDescription="@string/notification_more_settings"
android:background="@drawable/ripple_drawable_20dp"
android:src="@drawable/ic_settings"
- android:tint="?android:attr/colorAccent"
+ android:tint="?androidprv:attr/materialColorPrimary"
android:layout_alignParentEnd="true" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 852db1b..19a3f2f 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -17,6 +17,7 @@
<com.android.systemui.statusbar.notification.row.NotificationInfo
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/notification_guts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -102,7 +103,7 @@
android:contentDescription="@string/notification_app_settings"
android:src="@drawable/ic_info"
android:layout_toStartOf="@id/info"
- android:tint="@color/notification_guts_link_icon_tint"/>
+ android:tint="?androidprv:attr/materialColorPrimary"/>
<ImageButton
android:id="@+id/info"
android:layout_width="@dimen/notification_importance_toggle_size"
@@ -111,7 +112,7 @@
android:contentDescription="@string/notification_more_settings"
android:background="@drawable/ripple_drawable_20dp"
android:src="@drawable/ic_settings"
- android:tint="?android:attr/colorAccent"
+ android:tint="?androidprv:attr/materialColorPrimary"
android:layout_alignParentEnd="true" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 8b53680..6e541a7 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -23,7 +23,7 @@
android:orientation="vertical"
android:paddingTop="2dp"
android:paddingBottom="2dp"
- android:background="?androidprv:attr/colorSurface"
+ android:background="?androidprv:attr/materialColorSurfaceContainerHigh"
android:theme="@style/Theme.SystemUI">
<RelativeLayout
@@ -38,7 +38,7 @@
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:paddingStart="@*android:dimen/notification_content_margin_end"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:paddingEnd="4dp"/>
<ImageView
diff --git a/packages/SystemUI/res/layout/notification_snooze_option.xml b/packages/SystemUI/res/layout/notification_snooze_option.xml
index d42cc02..fa6f965 100644
--- a/packages/SystemUI/res/layout/notification_snooze_option.xml
+++ b/packages/SystemUI/res/layout/notification_snooze_option.xml
@@ -16,10 +16,11 @@
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="@*android:dimen/notification_headerless_min_height"
android:layout_marginStart="@*android:dimen/notification_content_margin_end"
android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
android:gravity="center_vertical"
android:textSize="14sp"
- android:textColor="?android:attr/textColorSecondary"/>
\ No newline at end of file
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index c1bac31..72424a13 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -41,7 +41,7 @@
android:layout_height="wrap_content"
>
<com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
+ style="@style/TextAppearance.NotificationFooterButton"
android:id="@+id/manage_text"
android:layout_width="wrap_content"
android:layout_height="48dp"
@@ -54,12 +54,11 @@
app:layout_constraintEnd_toStartOf="@id/dismiss_text"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
- android:textColor="@color/notif_pill_text"
android:contentDescription="@string/manage_notifications_history_text"
android:text="@string/manage_notifications_history_text"
/>
<com.android.systemui.statusbar.notification.row.FooterViewButton
- style="@style/TextAppearance.NotificationSectionHeaderButton"
+ style="@style/TextAppearance.NotificationFooterButton"
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="48dp"
@@ -70,7 +69,6 @@
app:layout_constraintStart_toEndOf="@id/manage_text"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
- android:textColor="@color/notif_pill_text"
android:contentDescription="@string/accessibility_clear_all"
android:text="@string/clear_all_notifications_text"
/>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml
index c4d8d55..53abe87 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml
@@ -40,7 +40,7 @@
android:layout_weight="1">
<TextView
- style="@style/TextAppearance.NotificationSectionHeaderButton"
+ style="@style/TextAppearance.NotificationSectionHeaderLabel"
android:id="@+id/header_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 6a192d4..39a1f1f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -69,12 +69,6 @@
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
-
- <include layout="@layout/volume_dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_dialog_stream_padding"
- android:layout_marginTop="6dp"/>
</FrameLayout>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index c9256ae..f35de05 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -62,7 +62,6 @@
android:background="@null"
android:layoutDirection="ltr"
android:rotation="270" />
- <include layout="@layout/volume_dnd_icon"/>
</FrameLayout>
<com.android.keyguard.AlphaOptimizedImageButton
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 24846d9..f68dd7b 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Stoor tans skermskoot in werkprofiel …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kon nie skermkiekie stoor nie"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Toestel moet ontsluit word voordat skermkiekie gestoor kan word"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer weer skermkiekie neem"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan nie skermkiekie stoor nie"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Saai tans uit"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Onbenoemde toestel"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen toestelle beskikbaar nie"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans vinnig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik die pylknoppie om die gemeenskaplike tutoriaal te begin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 23def28..20a8120 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ቅጽበታዊ ገፅ እይታን ወደ የስራ መገለጫ በማስቀመጥ ላይ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገፅ ዕይታ ተቀምጧል"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ቅጽበታዊ ገፅ ዕይታን ማስቀመጥ አልተቻለም"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ቅጽበታዊ ገፅ ዕይታ ከመቀመጡ በፊት መሣሪያ መከፈት አለበት"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ቅጽበታዊ ገፅ ዕይታን እንደገና ማንሳት ይሞክሩ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ቅጽበታዊ ገፅ እይታን ማስቀመጥ አልተቻለም"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"በመውሰድ ላይ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ያልተሰየመ መሣሪያ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ምንም መሣሪያዎች አይገኙም"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በፍጥነት ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"የጋራ አጋዥ ሥልጠናን ለመጀመር የቀስት አዝራሩ ላይ ጠቅ ያድርጉ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 80d63a2..03e019c 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في الملف الشخصي للعمل…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"يجب أن يتم فتح قفل الجهاز قبل حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"جرّب أخذ لقطة الشاشة مرة أخرى"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"يتعذّر حفظ لقطة الشاشة."</string>
@@ -159,7 +161,7 @@
<string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"النقش غير صحيح."</string>
<string name="biometric_dialog_wrong_password" msgid="69477929306843790">"كلمة مرور غير صحيحة"</string>
<string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"تم إجراء عدد كبير جدًا من المحاولات غير الصحيحة.\nأعد المحاولة خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
- <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"الطوارئ"</string>
+ <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"طوارئ"</string>
<string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"يُرجى إعادة المحاولة. المحاولة <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> من <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>"</string>
<string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"سيتم حذف بياناتك"</string>
<string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"عند إدخال نقش غير صحيح في المحاولة التالية، سيتم حذف بيانات هذا الجهاز."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"جارٍ الإرسال"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"جهاز لا يحمل اسمًا"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"لا يتوفر أي جهاز"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"لم يتم الاتصال بشبكة Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن سريعًا • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"انقر على زر السهم لبدء الدليل التوجيهي العام."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c845773..014e3b7 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"কৰ্মস্থানৰ প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীনশ্বট ছেভ কৰা হ’ল"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্ৰীনশ্বট ছেভ কৰিব পৰা নগ\'ল"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্ৰীনশ্বট ছেভ কৰিবলৈ ডিভাইচটো আনলক কৰিবই লাগিব"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"স্ক্ৰীনশ্বট আকৌ ল\'বলৈ চেষ্টা কৰক"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্ৰীনশ্বট ছেভ কৰিব নোৱাৰি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাষ্টিং"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নাম নথকা ডিভাইচ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইচ নাই"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্ৰুতগতিৰে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ কাঁড়চিহ্নৰ বুটামটোত ক্লিক কৰক"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 5aa26ba..d34ad9a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"İş profili skrinşotu saxlanılır…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinşotu yadda saxlamaq alınmadı"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinşotu saxlamazdan əvvəl cihaz kiliddən çıxarılmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skrinşotu yenidən çəkin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinşotu yadda saxlamaq mümkün olmadı"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayım"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Heç bir cihaz əlçatan deyil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sürətlə şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"İcma təlimatını başlatmaq üçün ox düyməsinə klikləyin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index cd36884..2b19e36 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Snimak ekrana se čuva na poslovnom profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Čuvanje snimka ekrana nije uspelo"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora da bude otključan da bi snimak ekrana mogao da se sačuva"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probajte da ponovo napravite snimak ekrana"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Čuvanje snimka ekrana nije uspelo"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Želite da započnete snimanje?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimaj zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk uređaja"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nije dostupan nijedan uređaj"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da biste započeli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Jedna aplikacija"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Delite ili snimite aplikaciju"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Želite da počnete snimanje ili prebacivanje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Pokreni"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je onemogućila ovu opciju"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Želite da započnete prebacivanje?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Započni prebacivanje"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Želite da počnete da delite?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se zaustavlja kada menjate aplikacije"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deli ovu aplikaciju"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 1b03c8b..301a042 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Захаванне здымка экрана ў працоўны профіль…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не атрымалася зрабіць здымак экрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Перад захаваннем здымка экрана трэба разблакіраваць прыладу"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Паспрабуйце зрабіць здымак экрана яшчэ раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не ўдалося захаваць здымак экрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ідзе перадача"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Прылада без назвы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма даступных прылад"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе хуткая зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Націсніце кнопку са стрэлкай, каб азнаёміцца з дапаможнікам"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 4ed1ad9..29f9d9e9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Екранната снимка се запазва в служебния профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не можа да се запази екранна снимка"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"За да бъде запазена екранната снимка, устройството трябва да бъде отключено"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Опитайте да направите екранна снимка отново"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Екранната снимка не може да се запази"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Предава се"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Устройство без име"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма налични устройства"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Не е установена връзка с Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бързо • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете върху бутона със стрелка, за да стартирате общия урок"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 426d38d..c2662d1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"অফিস প্রোফাইলে স্ক্রিনশট সেভ করা হচ্ছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্রিনশট সেভ করার আগে ডিভাইসটি অবশ্যই আনলক করতে হবে"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"আবার স্ক্রিনশট নেওয়ার চেষ্টা করুন"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্রিনশট সেভ করা যায়নি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাস্ট করা হচ্ছে"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নামবিহীন ডিভাইস"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইস উপলব্ধ নয়"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্রুত চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"কমিউনিটি টিউটোরিয়াল চালু করতে তীরচিহ্ন বোতামে ক্লিক করুন"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 4eed7b89..8d10454 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pohranjivanje snimka ekrana na radni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nije moguće sačuvati snimak ekrana"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Morate otključati uređaj da možete sačuvati snimak ekrana"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo snimiti ekran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće sačuvati snimak ekrana"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da pokrenete zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b55a79a..683c034 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"S\'està desant la captura al perfil de treball…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No s\'ha pogut desar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositiu ha d\'estar desbloquejat abans que la captura de pantalla es pugui desar"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prova de tornar a fer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No es pot desar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"En emissió"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositiu sense nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hi ha cap dispositiu disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fes clic al botó de la fletxa per iniciar el tutorial de la comunitat"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 05b0419..cbbf9df 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukládání snímku obrazovky do pracovního profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímek obrazovky se nepodařilo uložit"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Aby bylo možné uložit screenshot, zařízení musí být odemknuto"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zkuste snímek pořídit znovu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímek obrazovky se nepodařilo uložit"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Odesílání"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepojmenované zařízení"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nejsou dostupná žádná zařízení"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Rychlé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknutím na tlačítko s šipkou spustíte společný výukový program"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 5971034..57124bd 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gemmer screenshot på din arbejdsprofil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshottet kunne ikke gemmes"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheden skal være låst op, før du kan gemme screenshots"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv at tage et screenshot igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Dit screenshot kunne ikke gemmes."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Caster"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhed uden navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Der er ingen tilgængelige enheder"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader hurtigt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik på pilen for at starte den fælles vejledning"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index c773f71..8c308b9 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot wird in Arbeitsprofil gespeichert…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshot konnte nicht gespeichert werden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Damit Screenshots gespeichert werden können, muss das Gerät entsperrt sein"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Versuche noch einmal, den Screenshot zu erstellen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Screenshot kann nicht gespeichert werden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Wird übertragen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unbenanntes Gerät"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Keine Geräte verfügbar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string>
@@ -395,6 +398,8 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <!-- no translation found for communal_tutorial_indicator_text (700342473477865107) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index b6c95aa..69d877b 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Αποθήκευση στιγμιότ. οθόνης στο προφίλ εργασίας…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Μη δυνατή αποθήκευση του στιγμιότυπου οθόνης"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Η συσκευή πρέπει να ξεκλειδωθεί για να αποθηκευτεί το στιγμιότυπο οθόνης."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Δοκιμάστε να κάνετε ξανά λήψη του στιγμιότυπου οθόνης"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Δεν είναι δυνατή η αποθήκευση στιγμιότυπου οθόνης."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Μετάδοση"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ανώνυμη συσκευή"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Δεν υπάρχουν διαθέσιμες συσκευές"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Γρήγορη φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Κάντε κλικ στο κουμπί βέλους για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 0e282ff..f05d3c0 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1f3ab27..4934d73 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 52514d2..fa1a3f5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando cap. de pantalla en perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se pudo guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe estar desbloqueado para poder guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a hacer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se pudo guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitiendo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de flecha para iniciar el instructivo comunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4a3c064..005895b 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando captura en el perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe desbloquearse para que se pueda guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a intentar hacer la captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se puede guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Enviando"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de la flecha para iniciar el tutorial de la comunidad"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 08b489d..b4463d2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekraanipildi salvestamine tööprofiilile …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekraanipilti ei õnnestunud salvestada"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enne ekraanipildi salvestamist tuleb seade avada"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Proovige ekraanipilt uuesti jäädvustada"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekraanipilti ei saa salvestada"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Osatäitjad"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimeta seade"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ühtegi seadet pole saadaval"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kiirlaadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ühise õpetuse käivitamiseks klõpsake noolenuppu"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 19495bc..6701a77 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pantaila-argazkia laneko profilean gordetzen…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ezin izan da gorde pantaila-argazkia"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pantaila-argazkia gordetzeko, gailuak desblokeatuta egon beharko du"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Saiatu berriro pantaila-argazkia ateratzen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ezin da gorde pantaila-argazkia"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Igortzen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Izenik gabeko gailua"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ez dago gailurik erabilgarri"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Koloreen alderantzikatzea"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Bizkor kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Tutorial komuna hasteko, sakatu geziaren botoia"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 68f6c1d..705cc7c 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"درحال ذخیره کردن نماگرفت در نمایه کاری…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"نماگرفت ذخیره شد"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"نماگرفت ذخیره نشد"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"برای ذخیره کردن نماگرفت، قفل دستگاه باید باز باشد"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوباره نماگرفت بگیرید"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"نماگرفت ذخیره نمیشود"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"در حال فرستادن"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"دستگاه بدون نام"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"دستگاهی موجود نیست"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi وصل نیست"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن سریع • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"برای شروع آموزش گامبهگام عمومی، روی دکمه جهتنما کلیک کنید"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایینپر"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 934abad..d1c583b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Kuvakaappausta tallennetaan työprofiiliin…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kuvakaappauksen tallennus epäonnistui"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Laitteen lukitus täytyy avata ennen kuin kuvakaappaus voidaan tallentaa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Yritä ottaa kuvakaappaus uudelleen."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kuvakaappausta ei voi tallentaa"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Lähetetään"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimetön laite"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Laitteita ei ole käytettävissä"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu nopeasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Aloita yhteisöesittely klikkaamalla nuolipainiketta"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index fe90569..366d715 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sauv. de la capture dans le profil prof. en cours…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"L\'appareil doit être déverrouillé avant qu\'une capture d\'écran puisse être enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de faire une autre capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil à proximité"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquez sur le bouton de flèche pour démarrer le tutoriel communautaire"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 27a4bb6..8a51092 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Enregistrement de capture d\'écran dans profil pro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Vous devez déverrouiller l\'appareil pour que la capture d\'écran soit enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de nouveau de faire une capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquer sur la flèche pour démarrer le tutoriel collectif"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 2056c2f..e8aa65f 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gardando captura de pantalla no perfil de traballo"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Non se puido gardar a captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que se poida gardar a captura de pantalla, o dispositivo debe estar desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Volve tentar crear unha captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Non se puido gardar a captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sen nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Non hai dispositivos dispoñibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando rapidamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic no botón da frecha para iniciar o titorial comunitario"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 84be50a..15b1b44 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ઑફિસની પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"સ્ક્રીનશૉટ સાચવી શક્યાં નથી"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"સ્ક્રીનશૉટ સાચવવામાં આવે તે પહેલાં ડિવાઇસને અનલૉક કરવું જરૂરી છે"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ફરીથી સ્ક્રીનશૉટ લેવાનો પ્રયાસ કરો"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"સ્ક્રીનશૉટ સાચવી શકાતો નથી"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"કાસ્ટ કરી રહ્યાં છે"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"અનામાંકિત ઉપકરણ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"કોઈ ઉપકરણો ઉપલબ્ધ નથી"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ઝડપથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ઍરો બટન પર ક્લિક કરો"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2b0019f..d63f5cb 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"स्क्रीनशॉट, वर्क प्रोफ़ाइल में सेव किया जा रहा है…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव करने के लिए डिवाइस का अनलॉक होना ज़रूरी है"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट दोबारा लेने की कोशिश करें"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सकता"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्टिंग"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"अनाम डिवाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोई डिवाइस उपलब्ध नहीं"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • तेज़ चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, तीर के निशान वाले बटन पर क्लिक करें"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 23899fc..c3ea5ff2 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Spremanje snimke zaslona na poslovni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona spremljena"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snimka zaslona nije spremljena"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora biti otključan da bi se snimka zaslona mogla spremiti"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo napraviti snimku zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće spremiti snimku zaslona"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitiranje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Uređaj bez naziva"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • brzo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb sa strelicom da biste pokrenuli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e5b17d9..c894232 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Képernyőkép mentése a munkaprofilba…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nem sikerült a képernyőkép mentése"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Az eszközt fel kell oldani a képernyőkép mentése előtt"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Próbálja meg újra elkészíteni a képernyőképet"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nem lehetséges a képernyőkép mentése"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Átküldés"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Név nélküli eszköz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nem áll rendelkezésre eszköz"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Gyors töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kattintson a nyíl gombra a közösségi útmutató elindításához"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4a1c291..67d3886 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Սքրինշոթը պահվում է աշխատանքային պրոֆիլում…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Չհաջողվեց պահել սքրինշոթը"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Սքրինշոթը պահելու համար ապակողպեք սարքը։"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Փորձեք նորից"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Չհաջողվեց պահել սքրինշոթը"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Հեռարձակում"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Անանուն սարք"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Հասանելի սարքեր չկան"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Արագ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Սեղմեք սլաքի կոճակը՝ ուղեցույցը գործարկելու համար"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 18a7668..b1feb48 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan screenshot ke profil kerja …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Perangkat harus dibuka kuncinya agar screenshot dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Coba ambil screenshot lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Melakukan transmisi"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Perangkat tanpa nama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Perangkat tak tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan cepat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik tombol panah untuk memulai tutorial komunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 70bed46..9ff9c1a 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Vistar skjámynd á vinnusnið…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekki var hægt að vista skjámynd"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Taka verður tækið úr lás áður en hægt er að vista skjámynd"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prófaðu að taka skjámynd aftur"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekki er hægt að vista skjámynd"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Sendir út"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ónefnt tæki"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Engin tæki til staðar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hraðhleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Smelltu á örvahnappinn til að hefja samfélagsleiðsögnina"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index baffdb9..7fafa62 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvataggio screenshot nel profilo di lavoro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"È necessario sbloccare il dispositivo per poter salvare lo screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Riprova ad acquisire lo screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossibile salvare lo screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"In trasmissione"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo senza nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nessun dispositivo disponibile"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica veloce • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic sul pulsante Freccia per iniziare il tutorial della community"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 968a982..ced5895 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"צילום המסך נשמר בפרופיל העבודה…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"לא ניתן היה לשמור את צילום המסך"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"כדי שצילום המסך יישמר, צריך לבטל את הנעילה של המכשיר"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"אפשר לצלם שוב את המסך"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"לא ניתן לשמור את צילום המסך"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"מתבצעת העברה (cast)"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"אין חיבור ל-Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"אפשר ללחוץ על לחצן החץ כדי להפעיל את המדריך המשותף"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 798d42a..f5d5b74 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"スクリーンショットを仕事用プロファイルに保存中…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"スクリーンショット保存エラー"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"スクリーンショットを保存するには、デバイスのロックを解除する必要があります"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"スクリーンショットを撮り直してください"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"スクリーンショットを保存できません"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"キャストしています"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"名前のないデバイス"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"利用可能なデバイスがありません"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 急速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"矢印ボタンをクリックすると、コミュニティ チュートリアルが開始します"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f21a2a4..ae93966 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა სამუშაო პროფილში…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ეკრანის ანაბეჭდის შენახვა ვერ მოხერხდა"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"მოწყობილობა უნდა განიბლოკოს ეკრანის ანაბეჭდის შენახვა რომ შეძლოთ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ხელახლა ცადეთ ეკრანის ანაბეჭდის გაკეთება"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ეკრანის ანაბეჭდის შენახვა ვერ ხერხდება"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"გადაიცემა"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"უსახელო მოწყობილობა"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"მოწყობილობები მიუწვდომელია"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • სწრაფად იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"დააწკაპუნეთ ისრის ღილაკზე, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 746c02e..8c8efd1 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жұмыс профиліне сақталып жатыр…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сақталмады"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншот сақталуы үшін, құрылғы құлпын ашу керек."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Қайта скриншот жасап көріңіз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншотты сақтау мүмкін емес."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляциялануда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Атаусыз құрылғы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Құрылғылар қол жетімді емес"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жылдам зарядтау • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы оқулықты ашу үшін бағыт түймесін басыңыз."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9f3b991..cefdd27 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅកម្រងព័ត៌មានការងារ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុករូបថតអេក្រង់"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"មិនអាចរក្សាទុករូបថតអេក្រង់បានទេ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ត្រូវតែដោះសោឧបករណ៍ជាមុនសិន ទើបអាចរក្សាទុករូបថតអេក្រង់បាន"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"សាកល្បងថតរូបថតអេក្រង់ម្តងទៀត"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"មិនអាចរក្សាទុករូបថតអេក្រង់បានទេ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ការចាត់ថ្នាក់"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ឧបករណ៍ដែលមិនមានឈ្មោះ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"មិនមានឧបករណ៍ដែលអាចប្រើបាន"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមានការតភ្ជាប់ Wi-Fi ទេ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាសពណ៌"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការកែតម្រូវពណ៌"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្មយឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ចុចលើប៊ូតុងសញ្ញាព្រួញ ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរអ្នកប្រើ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយទាញចុះ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យទាំងអស់ក្នុងវគ្គនេះនឹងត្រូវលុប។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79eef72..6ec02ca 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಬೇಕು"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಪುನಃ ತೆಗೆದುಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ಹೆಸರಿಸದಿರುವ ಸಾಧನ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ಯಾವುದೇ ಸಾಧನಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್ವರ್ಶನ್"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ಸಮುದಾಯದ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಆ್ಯರೋ ಬಟನ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್ಡೌನ್ ಮೆನು"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 7985e4c..69525ee 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"직장 프로필에 스크린샷 저장 중…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"스크린샷을 저장할 수 없음"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"스크린샷을 저장하려면 기기를 잠금 해제해야 합니다."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"스크린샷을 다시 찍어 보세요."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"스크린샷을 저장할 수 없습니다."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"전송 중"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"이름이 없는 기기"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"사용 가능한 기기가 없습니다."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 고속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"공동 튜토리얼을 시작하려면 화살표 버튼을 클릭하세요."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index ded1f75..400afed 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жумуш профилине сакталууда…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сакталган жок"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншотту сактоо үчүн түзмөктүн кулпусун ачуу керек"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Скриншотту кайра тартып көрүңүз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншот сакталган жок"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Тышкы экранга чыгарылууда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Аты жок түзмөк"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Жеткиликтүү түзмөктөр жок"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Тез кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы үйрөткүчтү иштетүү үчүн жебе баскычын басыңыз"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 1e61b8a..b66e6dc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໃສ່ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ຈະຕ້ອງປົດລັອກອຸປະກອນກ່ອນບັນທຶກຮູບໜ້າຈໍ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ກະລຸນາລອງຖ່າຍຮູບໜ້າຈໍອີກຄັ້ງ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ກຳລັງສົ່ງສັນຍານ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ອຸປະກອນບໍ່ມີຊື່"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ບໍ່ມີອຸປະກອນທີ່ສາມາດໃຊ້ໄດ້"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບໄວ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ຄລິກໃສ່ປຸ່ມລູກສອນເພື່ອເລີ່ມຕົ້ນສອນການນຳໃຊ້ຊຸມຊົນ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 06c1369..955fe67 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Išsaugoma ekrano kopija darbo profilyje…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrano kopijos išsaugoti nepavyko"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Įrenginys turi būti atrakintas, kad būtų galima išsaugoti ekrano kopiją"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pabandykite padaryti ekrano kopiją dar kartą"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekrano kopijos išsaugoti nepavyko"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Perduodama"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Įrenginys be pavadinimo"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nėra pasiekiamų įrenginių"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sparčiai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Spustelėkite rodyklės mygtuką, kad paleistumėte bendruomenės mokomąją medžiagą"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index fdf3028..16cef4b 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Notiek ekrānuzņēmuma saglabāšana darba profilā…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrānuzņēmumu neizdevās saglabāt."</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Lai varētu saglabāt ekrānuzņēmumu, ierīcei ir jābūt atbloķētai."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Mēģiniet izveidot jaunu ekrānuzņēmumu."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nevar saglabāt ekrānuzņēmumu"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Notiek apraide…"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nenosaukta ierīce"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nav pieejamu ierīču."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ātrā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Noklikšķiniet uz bultiņas pogas, lai palaistu kopienas pamācību."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 80ef7bb..8babe8c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Се зачувува слика од екранот на вашиот работен профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уредот мора да биде отклучен за да може да се зачува слика од екранот"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Повторно обидете се да направите слика од екранот"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не може да се зачува слика од екранот"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Емитување"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименуван уред"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нема достапни уреди"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нема Wi-Fi врска"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни брзо • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете на копчето со стрелка за да го започнете заедничкото упатство"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5b1c80f..c5b6536 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"സ്ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കാനായില്ല"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നതിന് മുമ്പ് ഉപകരണം അൺലോക്ക് ചെയ്തിരിക്കണം"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"സ്ക്രീൻഷോട്ട് എടുക്കാൻ വീണ്ടും ശ്രമിക്കുക"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കാനാകുന്നില്ല"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"കാസ്റ്റുചെയ്യുന്നു"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"പേരിടാത്ത ഉപകരണം"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ഉപകരണങ്ങളൊന്നും ലഭ്യമല്ല"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്തിട്ടില്ല"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ അമ്പടയാള ബട്ടണിൽ ക്ലിക്ക് ചെയ്യുക"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 3f65931..b083c66 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Дэлгэцийн агшныг ажлын профайлд хадгалж байна…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Дэлгэцээс дарсан зургийг хадгалж чадсангүй"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Дэлгэцийн агшныг хадгалах боломжтой болохоос өмнө төхөөрөмжийн түгжээг тайлах ёстой"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Дэлгэцийн зургийг дахин дарж үзнэ үү"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Дэлгэцийн агшныг хадгалах боломжгүй"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Дамжуулж байна"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Нэргүй төхөөрөмж"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Төхөөрөмж байхгүй"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Хурдтай цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нийтийн практик хичээлийг эхлүүлэхийн тулд суман товчийг товшино уу"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 238aaca..5d9f67e 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलवर स्क्रीनशॉट सेव्ह करत आहे…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव्ह करण्याआधी डिव्हाइस अनलॉक करणे आवश्यक आहे"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट पुन्हा घेण्याचा प्रयत्न करा"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट सेव्ह करू शकत नाही"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्ट करत आहे"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"निनावी डिव्हाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोणतेही डिव्हाइसेस उपलब्ध नाहीत"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • वेगाने चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी ॲरो बटणावर क्लिक करा"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8c764f1..10e9859c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan tangkapan skrin ke profil kerja…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan tangkapan skrin"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Peranti mesti dibuka kunci sebelum tangkapan skrin dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Cuba ambil tangkapan skrin sekali lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan tangkapan skrin"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Menghantar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Peranti tidak bernama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Tiada peranti tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan cepat • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik butang anak panah untuk memulakan tutorial umum"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 883b9e9..dddc2fa 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"အလုပ်ပရိုဖိုင်တွင် ဖန်သားပြင်ဓာတ်ပုံ သိမ်းနေသည်…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"မျက်နှာပြင်ပုံကို သိမ်း၍မရပါ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ဖန်သားပြင်ဓာတ်ပုံကို မသိမ်းမီ စက်ပစ္စည်းကို လော့ခ်ဖွင့်ထားရမည်"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"မျက်နှာပြင်ပုံကို ထပ်ရိုက်ကြည့်ပါ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်း၍မရပါ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ကာစ်တင်"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"အမည်မတပ် ကိရိယာ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ကိရိယာများ မရှိ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အမြန်အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် မြားခလုတ်ကို နှိပ်ပါ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 53a4c52..ff32177 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Lagrer skjermdumpen i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kunne ikke lagre skjermdump"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheten må være låst opp før skjermdumpen kan lagres"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv å ta skjermdump på nytt"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan ikke lagre skjermdumpen"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhet uten navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ingen enheter er tilgjengelige"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi er ikke tilkoblet"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader raskt • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klikk på pilen for å starte fellesveiledningen"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3b31b3a..61e52bd 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"डिभाइस अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"प्रसारण गर्दै"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"बेनाम उपकरण"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कुनै उपकरणहरू उपलब्ध छैन"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi कनेक्ट गरिएको छैन"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • छिटो चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्युनल ट्युटोरियल सुरु गर्न एरो बटनमा क्लिक गर्नुहोस्"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 99311e3..d9385c7 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -33,7 +33,7 @@
<!-- The color of the text inside a notification -->
<color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
- <color name="notif_pill_text">@color/material_dynamic_neutral95</color>
+ <color name="notif_pill_text">@android:color/system_on_surface_dark</color>
<color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
<color name="notification_guts_sub_text_color">@color/GM2_grey_300</color>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 664af5e..7d21874 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot opslaan in werkprofiel…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kan screenshot niet opslaan"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Je moet het apparaat ontgrendelen voordat het screenshot kan worden opgeslagen"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer opnieuw een screenshot te maken"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan screenshot niet opslaan"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casten"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Naamloos apparaat"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen apparaten beschikbaar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Snel opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik op de pijl om de communitytutorial te starten"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index db9e0c5..38039cb 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ସ୍କ୍ରିନସଟ ସେଭ କରାଯାଉଛି…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ସ୍କ୍ରୀନ୍ଶଟ୍ ସେଭ୍ କରିହେବ ନାହିଁ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ସ୍କ୍ରିନସଟ୍ ସେଭ୍ କରିବା ପୂର୍ବରୁ ଡିଭାଇସକୁ ଅନଲକ୍ କରାଯିବା ଆବଶ୍ୟକ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ପୁଣିଥରେ ସ୍କ୍ରୀନ୍ଶଟ୍ ନେବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ସ୍କ୍ରିନସଟକୁ ସେଭ୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"କାଷ୍ଟିଙ୍ଗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ନାମହୀନ ଡିଭାଇସ୍"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"କୌଣସି ଡିଭାଇସ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"କଲର ଇନଭର୍ସନ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ତୀର ବଟନରେ କ୍ଲିକ କରନ୍ତୁ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍ ବଦଳାନ୍ତୁ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string>
@@ -660,7 +664,7 @@
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"ବର୍ତ୍ତମାନର"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ଫେରନ୍ତୁ"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ବିଜ୍ଞପ୍ତି"</string>
- <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀ\'ବୋର୍ଡ ସର୍ଟକଟ୍"</string>
+ <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
<string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"କୀ\'ବୋର୍ଡ୍ର ଲେଆଉଟ୍କୁ ବଦଳାନ୍ତୁ"</string>
<string name="keyboard_shortcut_clear_text" msgid="4679927133259287577">"ଟେକ୍ସଟ ଖାଲି କରନ୍ତୁ"</string>
<string name="keyboard_shortcut_search_list_title" msgid="1156178106617830429">"ସର୍ଟକଟଗୁଡ଼ିକ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 591c5e7..9011855 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦੁਬਾਰਾ ਲੈ ਕੇ ਦੇਖੋ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ਕਾਸਟਿੰਗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ਬਿਨਾਂ ਨਾਮ ਦਾ ਡੀਵਾਈਸ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ਕੋਈ ਡਿਵਾਈਸਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਤੀਰ ਬਟਨ \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 1951221..9b2b3cf 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Zapisuję zrzut ekranu w profilu służbowym…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nie udało się zapisać zrzutu ekranu"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Przed zapisaniem zrzutu ekranu musisz odblokować urządzenie"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Spróbuj jeszcze raz wykonać zrzut ekranu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nie można zapisać zrzutu ekranu."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Przesyłam"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Urządzenie bez nazwy"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Brak dostępnych urządzeń"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Szybkie ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknij przycisk strzałki, aby uruchomić samouczek społecznościowy"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 693a3a1..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c4f1f67..373cafb 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"A guardar captura de ecrã no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Não foi possível guardar a captura de ecrã"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"É necessário desbloquear o dispositivo para guardar a captura de ecrã"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Experimente voltar a efetuar a captura de ecrã."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não é possível guardar a captura de ecrã."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmissão"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Sem dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não ligado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar rapidamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial coletivo"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 693a3a1..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index a942332..1aabc3e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Se salvează captura în profilul de serviciu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblochezi dispozitivul"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Se proiectează"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispozitiv nedenumit"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Niciun dispozitiv disponibil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Dă clic pe butonul săgeată pentru a începe tutorialul pentru comunitate"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 68601d6..35b4c29 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Сохранение скриншота в рабочем профиле…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не удалось сохранить скриншот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Чтобы сохранить скриншот, разблокируйте устройство."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Попробуйте сделать скриншот снова."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не удалось сохранить скриншот."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Передача изображения"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Безымянное устройство"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нет доступных устройств"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Быстрая зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нажмите кнопку со стрелкой, чтобы ознакомиться с руководством"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 86cb3c3..f2dd421 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"කාර්යාල පැතිකඩ වෙත තිර රුව සුරකිමින්…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"තිර රුව සුරැකිය නොහැකි විය"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"තිර රුව සුරැකීමට පෙර උපාංගය අගුලු හැරිය යුතුය"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"තිර රුව නැවත ගැනීමට උත්සාහ කරන්න"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"තිර රුව සුරැකීමට නොහැකිය"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"කාස්ට් කිරීම"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"නම් නොකළ උපාංගය"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"උපාංග නොතිබේ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"වාර්ගික නිබන්ධනය ආරම්භ කිරීමට ඊතල බොත්තම ක්ලික් කරන්න"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 56b9f75..844f30c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukladá sa snímka obrazovky do pracovného profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímku obrazovky sa nepodarilo uložiť"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred uložením snímky obrazovky je potrebné zariadenie odomknúť"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skúste snímku urobiť znova"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímka obrazovky sa nedá uložiť"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prenáša sa"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepomenované zariadenie"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nie sú k dispozícii žiadne zariadenia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Sieť Wi‑Fi nie je pripojená"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa rýchlo • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ak chcete spustiť komunitný návod, kliknite na tlačidlo so šípkou"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index b72f750..1eab754 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Shranjevanje posnetka zaslona v delovni profil …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Posnetka zaslona ni bilo mogoče shraniti"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred shranjevanjem posnetka zaslona morate odkleniti napravo"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Poskusite znova ustvariti posnetek zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Posnetka zaslona ni mogoče shraniti"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Predvajanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovana naprava"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Na voljo ni nobene naprave"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Povezava Wi-Fi ni vzpostavljena"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hitro polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb s puščico, da zaženete vadnico za skupnost"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 92f6f9c..cafd6a0 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pamja e ekranit po ruhet te profili i punës…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Pamja e ekranit nuk mund të ruhej"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pajisja duhet të shkyçet para se të mund të ruhet pamja e ekranit"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Provo ta nxjerrësh përsëri pamjen e ekranit"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Pamja e ekranit nuk mund të ruhet"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Po transmeton"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Pajisje e paemërtuar"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nuk ofrohet për përdorim asnjë pajisje"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet shpejt • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliko mbi butonin e shigjetës për të filluar udhëzuesin e përbashkët"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 7c0d9be..8c1450a1 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Снимак екрана се чува на пословном профилу…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Чување снимка екрана није успело"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уређај мора да буде откључан да би снимак екрана могао да се сачува"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Пробајте да поново направите снимак екрана"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Чување снимка екрана није успело"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Желите да започнете снимање?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Започни снимање"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук уређаја"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Пребацивање"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименовани уређај"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Није доступан ниједан уређај"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Брзо се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликните на дугме са стрелицом да бисте започели заједнички водич"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Једна апликација"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Делите или снимите апликацију"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Желите да почнете снимање или пребацивање помоћу апликације <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Покрени"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је онемогућила ову опцију"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Желите да започнете пребацивање?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Започни пребацивање"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Желите да почнете да делите?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Дељење се зауставља када мењате апликације"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Дели ову апликацију"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 323ce11f..59bb1f5 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sparar skärmbild i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmbilden har sparats"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Det gick inte att spara skärmbilden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skärmbilden kan bara sparas om enheten är upplåst"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Testa att ta en skärmbild igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Det gick inte att spara skärmbilden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Castar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Namnlös enhet"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Inga tillgängliga enheter"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas snabbt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klicka på pilknappen för att börja med gruppguiden"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 304910a..b977dc4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Inahifadhi picha ya skrini kwenye wasifu wa kazini…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Imeshindwa kuhifadhi picha ya skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ni sharti ufungue kifaa kabla ya kuhifadhi picha ya skrini"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Jaribu kupiga picha ya skrini tena"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Imeshindwa kuhifadhi picha ya skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Inatuma"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Kifaa hakina jina"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Hakuna vifaa vilivyopatikana"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji kwa kasi • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Bofya kwenye kitufe cha kishale ili kuanzisha mafunzo ya jumuiya"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3ac5e2e..3d05824 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"பணிக் கணக்கில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ஸ்கிரீன் ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ஸ்கிரீன்ஷாட் சேமிக்கப்படுவதற்கு முன்பு சாதனம் அன்லாக் செய்யப்பட வேண்டும்"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ஸ்கிரீன் ஷாட்டை மீண்டும் எடுக்க முயலவும்"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ஸ்கிரீன்ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"அனுப்புகிறது"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"பெயரிடப்படாத சாதனம்"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"சாதனங்கள் இல்லை"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்ஷன்"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • வேகமாகச் சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"சமூகப் பயிற்சியைத் தொடங்க அம்புக்குறி பட்டனைக் கிளிக் செய்யுங்கள்"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 0526f95..6d68b63 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"స్క్రీన్షాట్ను వర్క్ ప్రొఫైల్కు సేవ్ చేస్తోంది…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్షాట్ సేవ్ చేయబడింది"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"స్క్రీన్షాట్ని సేవ్ చేయడం సాధ్యం కాలేదు"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"స్క్రీన్షాట్ సేవ్ అవ్వకముందే పరికరం అన్లాక్ చేయబడాలి"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"స్క్రీన్షాట్ తీయడానికి మళ్లీ ట్రై చేయండి"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"స్క్రీన్షాట్ను సేవ్ చేయడం సాధ్యపడలేదు"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ప్రసారం చేస్తోంది"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"పేరులేని పరికరం"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"పరికరాలు ఏవీ అందుబాటులో లేవు"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • వేగంగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"కమ్యూనల్ ట్యుటోరియల్ను ప్రారంభించడానికి బాణం బటన్పై క్లిక్ చేయండి"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్డౌన్ మెనూ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 8949360..3f5a47e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"กำลังบันทึกภาพหน้าจอไปยังโปรไฟล์งาน…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"บันทึกภาพหน้าจอไม่ได้"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ต้องปลดล็อกอุปกรณ์ก่อนจึงจะบันทึกภาพหน้าจอได้"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ลองบันทึกภาพหน้าจออีกครั้ง"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"บันทึกภาพหน้าจอไม่ได้"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"กำลังส่ง"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"อุปกรณ์ที่ไม่มีชื่อ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ไม่มีอุปกรณ์ที่สามารถใช้ได้"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างเร็ว • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"คลิกปุ่มลูกศรเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e5c11df..3fac637 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sine-save ang screenshot sa profile sa trabaho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Hindi ma-save ang screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Dapat naka-unlock ang device bago ma-save ang screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Subukang kumuhang muli ng screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Hindi ma-save ang screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Nagka-cast"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Walang pangalang device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Walang available na mga device"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabilis na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"I-click ang arrow button para simulan ang communal na tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 3552d92..ca357b41 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekran görüntüsü iş profiline kaydediliyor…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekran görüntüsü kaydedilemedi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ekran görüntüsünün kaydedilebilmesi için cihazın kilidi açık olmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tekrar ekran görüntüsü almayı deneyin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekran görüntüsü kaydedilemiyor"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayınlanıyor"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Kullanılabilir cihaz yok"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hızlı şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ortak eğitimi başlatmak için ok düğmesini tıklayın"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 2e6aea9..6078e31 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Зберігання знімка екрана в робочому профілі…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не вдалося зберегти знімок екрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Щоб зберегти знімок екрана, розблокуйте пристрій"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Спробуйте зробити знімок екрана ще раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не вдалося зберегти знімок екрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляція"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Пристрій без назви"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Немає пристроїв"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Швидке заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Натисніть кнопку зі стрілкою, щоб відкрити спільний навчальний посібник"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 2e2fc41..5cf763f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"اسکرین شاٹ دفتری پروفائل میں محفوظ کیا جا رہا ہے…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکا"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"اسکرین شاٹ محفوظ کرنے سے پہلے آلے کو غیر مقفل کرنا ضروری ہے"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوبارہ اسکرین شاٹ لینے کی کوشش کریں"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکتا"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"کاسٹنگ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"بغیر نام والا آلہ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"کوئی آلات دستیاب نہیں ہیں"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi سے منسلک نہیں ہے"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • تیزی سے چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"کمیونل ٹیوٹوریل شروع کرنے کے لیے تیر کے نشان والے بٹن پر کلک کریں"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a8654d5..412dd8a 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -20,8 +20,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4811759950673118541">"Tizim interfeysi"</string>
- <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash funksiyasi yoqilsinmi?"</string>
- <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> batareya quvvati qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
+ <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash yoqilsinmi?"</string>
+ <string name="battery_low_description" msgid="3282977755476423966">"Batareya quvvati <xliff:g id="PERCENTAGE">%s</xliff:g> qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_intro" msgid="5148725009653088790">"Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> qoldi"</string>
<string name="invalid_charger_title" msgid="938685362320735167">"USB orqali quvvatlash imkonsiz"</string>
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Skrinshot ish profiliga saqlanmoqda…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinshot saqlanmadi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinshotni saqlashdan oldin qurilma qulflanmagan boʻlishi lozim"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Qayta skrinshot olib ko‘ring"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinshot saqlanmadi"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Translatsiya qilinmoqda"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nomsiz qurilma"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Qurilmalar topilmadi"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tarmoqqa ulanmagan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Tez quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Qoʻllanma bilan tanishish uchun strelka tugmasini bosing"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 5443745..45dfc45 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Đang lưu ảnh chụp màn hình vào hồ sơ công việc…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Không thể lưu ảnh chụp màn hình"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Bạn phải mở khóa thiết bị để chúng tôi có thể lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Hãy thử chụp lại màn hình"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Không thể lưu ảnh chụp màn hình"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Đang truyền"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Thiết bị không có tên"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Không có thiết bị nào"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc nhanh • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Nhấp vào nút mũi tên để bắt đầu xem hướng dẫn chung"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 44c163b..17a66787 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在将屏幕截图保存到工作资料…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"无法保存屏幕截图"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必须先解锁设备,然后才能保存屏幕截图"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"请再次尝试截屏"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"无法保存屏幕截图"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名设备"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"没有可用设备"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在快速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"点击箭头按钮,即可启动公共教程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f45b2fe..48fbf7c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存至工作設定檔…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕擷取畫面"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再嘗試拍攝螢幕擷取畫面"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在放送"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"按一下箭咀鍵,即可開始共用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 5a5bb9d..55d4cb6 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存到工作資料夾…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕截圖"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再次嘗試拍攝螢幕截圖"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"點選箭頭按鈕,即可開始通用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 8b6bfae..b883b9a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ilondoloza isithombe-skrini kuphrofayela yomsebenzi…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ayikwazanga ukulondoloza isithombe-skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Idivayisi kufanele ivulwe ngaphambi kokuthi isithombe-skrini singalondolozwa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zama ukuthatha isithombe-skrini futhi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ayikwazi ukulondoloza isithombe-skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ukusakaza"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Idivayisi engenalo igama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ayikho idivayisi etholakalayo"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja ngokushesha • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Chofoza inkinobho yomcibisholo ukuze uqalise isifundo somphakathi"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index e942258..0620355 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -115,7 +115,7 @@
<!-- Chosen so fill over background matches single tone -->
<color name="dark_mode_qs_icon_color_dual_tone_fill">#99000000</color>
- <color name="notif_pill_text">@color/material_dynamic_neutral10</color>
+ <color name="notif_pill_text">@android:color/system_on_surface_light</color>
<!-- Keyboard shortcuts colors -->
<color name="ksh_application_group_color">#fff44336</color>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 05f4334..74d435d 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 9e2ebf6..4c520444 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -466,7 +466,7 @@
<string name="accessibility_bluetooth_device_icon">Bluetooth device icon</string>
<!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] -->
- <string name="accessibility_bluetooth_device_settings_gear">Bluetooth device settings gear</string>
+ <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string>
<!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_unknown">Battery percentage unknown.</string>
@@ -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]-->
@@ -3022,8 +3022,6 @@
<string name="dream_overlay_status_bar_mic_off">Mic is off</string>
<!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
- <!-- Content description for the assistant attention indicator [CHAR LIMIT=NONE] -->
- <string name="dream_overlay_status_bar_assistant_attention_indicator">Assistant is listening</string>
<!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_notification_indicator">{count, plural,
=1 {# notification}
@@ -3209,7 +3207,7 @@
<string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
<!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
- <string name="assistant_attention_content_description">Assistant attention on</string>
+ <string name="assistant_attention_content_description">User presence is detected</string>
<!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
<string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 084cb88..7ce530f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -597,34 +597,34 @@
<style name="TextAppearance.NotificationImportanceChannel">
<item name="android:textSize">@dimen/notification_importance_channel_text</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- <item name="android:textColor">@color/notification_guts_header_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:textSize">@dimen/notification_importance_channel_text</item>
</style>
<style name="TextAppearance.NotificationImportanceChannelGroup">
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">@color/notification_guts_header_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
</style>
<style name="TextAppearance.NotificationImportanceApp">
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">@color/notification_guts_sub_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
<item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
</style>
<style name="TextAppearance.NotificationImportanceHeader">
<item name="android:textSize">@dimen/notification_importance_header_text</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">@color/notification_guts_header_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="TextAppearance.NotificationImportanceDetail">
<item name="android:textSize">@dimen/notification_importance_description_text</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
- <item name="android:textColor">@color/notification_guts_sub_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
<item name="android:gravity">center</item>
</style>
@@ -636,9 +636,27 @@
</style>
<style
+ name="TextAppearance.NotificationSectionHeaderLabel"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:minWidth">0dp</item>
+ </style>
+
+ <style
name="TextAppearance.NotificationSectionHeaderButton"
parent="@android:style/Widget.DeviceDefault.Button.Borderless">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:minWidth">0dp</item>
+ </style>
+
+ <style
+ name="TextAppearance.NotificationFooterButton"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
<item name="android:minWidth">0dp</item>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
rename to packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
index 0aa6b0b..9b7cd70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
@@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.dagger.qualifiers
-package com.android.systemui.qs.tiles.base.interactor
+import javax.inject.Qualifier
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 7b2e1af..e459034 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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.
@@ -18,13 +18,25 @@
import android.os.Trace
import android.os.TraceNameSupplier
+import android.util.Log
+import com.android.systemui.util.tracing.TraceContextElement
+import com.android.systemui.util.tracing.TraceData
+import com.android.systemui.util.tracing.TraceData.Companion.FIRST_VALID_SPAN
+import com.android.systemui.util.tracing.TraceData.Companion.INVALID_SPAN
+import com.android.systemui.util.tracing.threadLocalTrace
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.random.Random
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
/**
* Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -44,6 +56,10 @@
class TraceUtils {
companion object {
+ const val TAG = "TraceUtils"
+ private const val DEBUG_COROUTINE_TRACING = false
+ const val DEFAULT_TRACK_NAME = "AsyncTraces"
+
inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
return Runnable { traceSection(tag) { block() } }
}
@@ -73,7 +89,7 @@
* under a single track.
*/
inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync("AsyncTraces", method, block)
+ traceAsync(DEFAULT_TRACK_NAME, method, block)
/**
* Creates an async slice in a track with [trackName] while [block] runs.
@@ -93,16 +109,313 @@
}
/**
- * Convenience method to avoid one indentation level when we want to add a trace when
- * launching a coroutine
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
*/
- fun <T> CoroutineScope.tracedAsync(
- method: String,
+ inline fun CoroutineScope.launch(
+ crossinline spanName: () -> String,
context: CoroutineContext = EmptyCoroutineContext,
- start: CoroutineStart = CoroutineStart.DEFAULT,
- block: suspend () -> T
- ): Deferred<T> {
- return async(context, start) { traceAsync(method) { block() } }
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun CoroutineScope.launch(
+ spanName: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> CoroutineScope.async(
+ crossinline spanName: () -> String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> CoroutineScope.async(
+ spanName: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend () -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
+ * file. It should only every be set to true during startup. Once true, it cannot be set to
+ * false again.
+ */
+ var coroutineTracingIsEnabled = false
+ set(v) {
+ if (v) field = true
+ }
+
+ /**
+ * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
+ * thread that is currently executing the [block] of work. If the [block] is suspended, all
+ * trace sections added using this API will end until the [block] is resumed, which could
+ * happen either on this thread or on another thread. If a child coroutine is started, it
+ * will inherit the trace sections of its parent. The child will continue to print these
+ * trace sections whether or not the parent coroutine is still running them.
+ *
+ * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
+ * Otherwise, the trace sections will be dropped.
+ *
+ * For example, in the following trace, Thread #1 ran some work, suspended, then continued
+ * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
+ * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
+ * Meanwhile Thread #3 is still printing trace sections from its parent because they were
+ * copied when it was created. There is no way for the parent to communicate to the child
+ * that it marked these slices as completed. While this might seem counterintuitive, it
+ * allows us to pinpoint the origin of the child coroutine's work.
+ *
+ * ```
+ * Thread #1 | [==== Slice A ====] [==== Slice A ====]
+ * | [==== B ====] [=== B ===]
+ * --------------------------------------------------------------------------------------
+ * Thread #2 | [====== Slice A ======]
+ * | [========= B =========]
+ * | [===== C ======]
+ * --------------------------------------------------------------------------------------
+ * Thread #3 | [== Slice A ==] [== Slice A ==]
+ * | [===== B =====] [===== B =====]
+ * | [===== C =====] [===== C =====]
+ * | [=== D ===]
+ * ```
+ *
+ * @param name The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend inline fun <T> traceCoroutine(
+ spanName: Lazy<String>,
+ crossinline block: suspend () -> T
+ ): T {
+ // For coroutine tracing to work, trace spans must be added and removed even when
+ // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
+ // coroutine resumes when tracing is active, we won't know its name.
+ val tracer = getTraceData(spanName)
+ val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
+
+ // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
+ // of the COROUTINE_TRACING feature flag.
+ val asyncTraceCookie =
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
+ Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
+ else INVALID_SPAN
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackBegin(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ spanName.value,
+ asyncTraceCookie
+ )
+ }
+ try {
+ return block()
+ } finally {
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackEnd(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ asyncTraceCookie
+ )
+ }
+ tracer?.endSpan(coroutineSpanCookie)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
+ if (!coroutineTracingIsEnabled) {
+ logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
+ } else if (coroutineContext[TraceContextElement] == null) {
+ logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
+ } else {
+ return threadLocalTrace.get().also {
+ if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
+ }
+ }
+ return null
+ }
+
+ private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
+ if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
+ }
+ }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ spanName: String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazyOf(spanName)) { block() }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ crossinline spanName: () -> String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has begun running __on
+ * the current thread__. This must be followed by a corresponding call to [endSlice] in a
+ * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
+ * idle again and starts running other, unrelated work).
+ *
+ * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
+ * follows:
+ * ```
+ * Thread #1 | [==========================]
+ * | [==============]
+ * | [====]
+ * ```
+ *
+ * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
+ * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
+ * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
+ * use the term "slice" instead of "section" to be consistent with Perfetto.
+ *
+ * # Avoiding malformed traces
+ *
+ * Improper usage of this API will lead to malformed traces with long slices that sometimes
+ * never end. This will look like the following:
+ * ```
+ * Thread #1 | [===================================================================== ...
+ * | [==============] [====================================== ...
+ * | [=======] [======] [===================== ...
+ * | [=======]
+ * ```
+ *
+ * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
+ * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
+ * technically okay to call from a suspending function if that function were to only wrap
+ * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
+ * calls could be mistakenly added to that block as the code is refactored.
+ *
+ * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
+ * it with a call to [endSlice] inside that callback, even if the callback runs on the same
+ * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
+ * before the thread became idle and started running unrelated work.
+ *
+ * @param sliceName The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ fun beginSlice(sliceName: String) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has ended. This call must
+ * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
+ * information regarding usage.
+ *
+ * @see beginSlice
+ * @see traceCoroutine
+ */
+ fun endSlice() {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the current thread.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Thread #1 | [==============] [======]
+ * | [====] ^
+ * | ^
+ * ```
+ *
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instant(eventName: String) {
+ Trace.instant(Trace.TRACE_TAG_APP, eventName)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the given track.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Async | [==============] [======]
+ * Track | [====] ^
+ * Name | ^
+ * ```
+ *
+ * @param trackName The track where the event should appear in the trace.
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instantForTrack(trackName: String, eventName: String) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
new file mode 100644
index 0000000..4d8c545
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.instant
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CopyableThreadContextElement
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+@OptIn(DelicateCoroutinesApi::class)
+@ExperimentalCoroutinesApi
+class TraceContextElement(private val traceData: TraceData = TraceData()) :
+ CopyableThreadContextElement<TraceData?> {
+
+ companion object Key : CoroutineContext.Key<TraceContextElement>
+
+ override val key: CoroutineContext.Key<TraceContextElement> = Key
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun updateThreadContext(context: CoroutineContext): TraceData? {
+ val oldState = threadLocalTrace.get()
+ oldState?.endAllOnThread()
+ threadLocalTrace.set(traceData)
+ instant("resuming ${context[CoroutineDispatcher]}")
+ traceData.beginAllOnThread()
+ return oldState
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
+ instant("suspending ${context[CoroutineDispatcher]}")
+ traceData.endAllOnThread()
+ threadLocalTrace.set(oldState)
+ oldState?.beginAllOnThread()
+ }
+
+ override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
+ return TraceContextElement(traceData.copy())
+ }
+
+ override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
+ return TraceContextElement(traceData.copy())
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
new file mode 100644
index 0000000..0ae58fc
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.tracing
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.util.TraceUtils.Companion.beginSlice
+import com.android.systemui.util.TraceUtils.Companion.endSlice
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.random.Random
+
+/**
+ * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
+ * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
+ *
+ * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
+ * in does not have a [TraceContextElement].
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+val threadLocalTrace = ThreadLocal<TraceData?>()
+
+/**
+ * Used for storing trace sections so that they can be added and removed from the currently running
+ * thread when the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+class TraceData {
+ private var slices = mutableListOf<TraceSection>()
+
+ /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
+ fun beginAllOnThread() {
+ slices.forEach { beginSlice(it.name) }
+ }
+
+ /**
+ * Removes all current trace slices from the current thread. Called when coroutine is suspended.
+ */
+ fun endAllOnThread() {
+ for (i in 0..slices.size) {
+ endSlice()
+ }
+ }
+
+ /**
+ * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
+ * will also be added to the current thread immediately. This slice will not propagate to parent
+ * coroutines, or to child coroutines that have already started. The unique ID is used to verify
+ * that the [endSpan] is corresponds to a [beginSpan].
+ */
+ fun beginSpan(name: String): Int {
+ val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
+ slices.add(newSlice)
+ beginSlice(name)
+ return newSlice.id
+ }
+
+ /**
+ * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
+ * state is isolated from the parent.
+ */
+ fun copy(): TraceData {
+ return TraceData().also { it.slices.addAll(slices) }
+ }
+
+ /**
+ * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
+ * trace slice will immediately be removed from the current thread. This information will not
+ * propagate to parent coroutines, or to child coroutines that have already started.
+ */
+ fun endSpan(id: Int) {
+ val v = slices.removeLast()
+ if (v.id != id) {
+ if (STRICT_MODE) {
+ throw IllegalArgumentException(errorMsg)
+ } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, errorMsg)
+ }
+ }
+ endSlice()
+ }
+
+ companion object {
+ private const val TAG = "TraceData"
+ const val INVALID_SPAN = -1
+ const val FIRST_VALID_SPAN = 1
+
+ /**
+ * If true, throw an exception instead of printing a warning when trace sections beginnings
+ * and ends are mismatched.
+ */
+ private val STRICT_MODE = Build.IS_ENG
+
+ private const val errorMsg =
+ "Mismatched trace section. This likely means you are accessing the trace local " +
+ "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
+ " This could happen if you are using a global dispatcher like Dispatchers.IO." +
+ " To fix this, use one of the coroutine contexts provided by the dagger scope " +
+ "(e.g. \"@Main CoroutineContext\")."
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
new file mode 100644
index 0000000..b70c497
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+
+/**
+ * Represents a section of code executing in a coroutine. This can be split up into multiple slices
+ * on different threads as the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @param name the name of the slice to appear on the current thread's track.
+ * @param id used for matching the beginning and end of trace sections and validating correctness
+ * @see traceCoroutine
+ */
+data class TraceSection(
+ val name: String,
+ val id: Int,
+)
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index fae9fec..a263361 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@
@IntoSet
abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+ @Binds
+ @IntoSet
+ abstract fun bindsNotOccludedCondition(
+ impl: NotOccludedCondition
+ ): ConditionalRestarter.Condition
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 7aacb4e..9684d5e 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@
@IntoSet
abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+ @Binds
+ @IntoSet
+ abstract fun bindsNotOccludedCondition(
+ impl: NotOccludedCondition
+ ): ConditionalRestarter.Condition
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index b186018..3757274 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -318,9 +318,8 @@
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 @@
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 873c3d9..1cfa816 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -39,10 +39,10 @@
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 @@
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/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3c8301f..7bf3e8f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -38,10 +38,10 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -50,14 +50,22 @@
import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.regionsampling.RegionSampler;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -68,6 +76,8 @@
import javax.inject.Inject;
+import kotlinx.coroutines.DisposableHandle;
+
/**
* Injectable controller for {@link KeyguardClockSwitch}.
*/
@@ -84,6 +94,12 @@
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
+ private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
+ private final ConfigurationState mConfigurationState;
+ private final ConfigurationController mConfigurationController;
+ private final DozeParameters mDozeParameters;
+ private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -107,10 +123,13 @@
private boolean mShownOnSecondaryDisplay = false;
private boolean mOnlyClock = false;
private boolean mIsActiveDreamLockscreenHosted = false;
- private FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private KeyguardInteractor mKeyguardInteractor;
private final DelayableExecutor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
+ private DisposableHandle mAodIconsBindJob;
+ @Nullable private NotificationIconContainer mAodIconContainer;
+
@VisibleForTesting
final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
(Boolean isLockscreenHosted) -> {
@@ -151,26 +170,38 @@
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
LockscreenSmartspaceController smartspaceController,
+ ConfigurationController configurationController,
+ ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main DelayableExecutor uiExecutor,
DumpManager dumpManager,
ClockEventController clockEventController,
@KeyguardClockLog LogBuffer logBuffer,
+ NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
+ ConfigurationState configurationState,
+ DozeParameters dozeParameters,
+ AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
KeyguardInteractor keyguardInteractor,
- FeatureFlags featureFlags) {
+ FeatureFlagsClassic featureFlags) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mSmartspaceController = smartspaceController;
+ mConfigurationController = configurationController;
+ mScreenOffAnimationController = screenOffAnimationController;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
mClockEventController = clockEventController;
mLogBuffer = logBuffer;
+ mAodIconsViewModel = aodIconsViewModel;
+ mConfigurationState = configurationState;
+ mDozeParameters = dozeParameters;
+ mAodIconViewStore = aodIconViewStore;
mView.setLogBuffer(mLogBuffer);
mFeatureFlags = featureFlags;
mKeyguardInteractor = keyguardInteractor;
@@ -316,6 +347,8 @@
int getNotificationIconAreaHeight() {
if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
return 0;
+ } else if (NotificationIconContainerRefactor.isEnabled()) {
+ return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
} else {
return mNotificationIconAreaController.getHeight();
}
@@ -533,7 +566,27 @@
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
- mNotificationIconAreaController.setupAodIcons(nic);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mAodIconsBindJob != null) {
+ mAodIconsBindJob.dispose();
+ }
+ if (nic != null) {
+ nic.setOnLockScreen(true);
+ mAodIconsBindJob = NotificationIconContainerViewBinder.bind(
+ nic,
+ mAodIconsViewModel,
+ mConfigurationState,
+ mConfigurationController,
+ mDozeParameters,
+ mFeatureFlags,
+ mScreenOffAnimationController,
+ mAodIconViewStore
+ );
+ mAodIconContainer = nic;
+ }
+ } else {
+ mNotificationIconAreaController.setupAodIcons(nic);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 2e21255..3d8aaaf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -65,15 +65,23 @@
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 @@
| 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 681aa70..175544d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -89,10 +89,7 @@
@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 @@
}
@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 b7d1171..376933d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -41,6 +41,9 @@
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 7101ed5..1b6112f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -755,7 +755,7 @@
}
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 13f9d3e..05fb5fa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -246,7 +246,7 @@
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 7d6240b..f19a9ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -441,7 +441,6 @@
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 @@
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 @@
}
}
- 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 @@
}
/**
+ * @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 @@
}
@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 fa07072..5bf8d63 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 @@
)
}
- 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/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 584357b..0bd4859 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1461,8 +1461,7 @@
mDragView.setColorFilter(filter);
}
- @VisibleForTesting
- void setBounceEffectDuration(int duration) {
+ private void setBounceEffectDuration(int duration) {
mBounceEffectDuration = duration;
}
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 105de16c..cd8bef1 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 com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.systemui.Flags;
import java.util.HashMap;
@@ -91,16 +92,48 @@
}
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 @@
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 @@
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 @@
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 @@
@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 @@
DynamicAnimation::isRunning);
if (!areAnimationsRunning) {
onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(),
- mMenuView.getTranslationY()));
+ mMenuView.getTranslationY()), writeToPosition);
}
})
.setStartVelocity(velocity);
@@ -281,7 +311,8 @@
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 @@
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 @@
mPositionAnimations.get(property).cancel();
}
+ @VisibleForTesting
+ DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) {
+ return mPositionAnimations.getOrDefault(property, null);
+ }
+
void onDraggingStart() {
mMenuView.onDraggingStart();
}
@@ -361,9 +401,9 @@
.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 @@
}
}
- 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 @@
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 @@
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 e1612b0..ea5a56c 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 @@
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 @@
private boolean mIsMoveToTucked;
+ private final MenuAnimationController mMenuAnimationController;
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
@@ -197,8 +197,30 @@
}
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 b6e8997..fbca022 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 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 @@
mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);
mMenuView.onEdgeChanged();
- mMenuView.onPositionChanged();
+ mMenuView.onPositionChanged(/* animateMovement = */ true);
mImeInsetsRect.set(imeInsetsRect);
}
@@ -362,6 +363,10 @@
mMenuAnimationController.startTuckedAnimationPreview();
}
+
+ if (Flags.floatingMenuImeDisplacementAnimation()) {
+ mMenuView.onArrivalAtPosition();
+ }
}
private CharSequence getMigrationMessage() {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 453a7a6..8ea867b 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -28,6 +28,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.TraceUtils.Companion.async
+import com.android.systemui.util.TraceUtils.Companion.withContext
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlin.math.max
@@ -263,7 +265,7 @@
* not being throttled.
*/
private suspend fun refreshThrottling(): Long {
- return withContext(backgroundDispatcher) {
+ return withContext("$TAG#refreshThrottling", backgroundDispatcher) {
val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
val deadline = async { repository.getThrottlingEndTimestamp() }
val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
@@ -311,6 +313,10 @@
DomainLayerAuthenticationMethodModel.Pattern
}
}
+
+ companion object {
+ const val TAG = "AuthenticationInteractor"
+ }
}
/** Result of a user authentication attempt. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 272e0f2..934f9f9 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 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 @@
}
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 56e3f39..2f493ac 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 @@
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 @@
}
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 @@
}
}
}
- 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 75331f0..1086897 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 @@
/** 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 @@
) {
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 21578f4..56dfa5ed 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 @@
/** 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/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
new file mode 100644
index 0000000..7bca86e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+package com.android.systemui.common.ui
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.AttrRes
+import androidx.annotation.ColorInt
+import androidx.annotation.DimenRes
+import androidx.annotation.LayoutRes
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onThemeChanged
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.view.bindLatest
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Configuration-aware-state-tracking utilities. */
+class ConfigurationState
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ @Application private val context: Context,
+ private val layoutInflater: LayoutInflater,
+) {
+ /**
+ * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+ * configuration.
+ *
+ * @see android.content.res.Resources.getDimensionPixelSize
+ */
+ fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
+ return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+ context.resources.getDimensionPixelSize(id)
+ }
+ }
+
+ /**
+ * Returns a [Flow] that emits a color that is kept in sync with the device theme.
+ *
+ * @see Utils.getColorAttrDefaultColor
+ */
+ fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
+ return configurationController.onThemeChanged.emitOnStart().map {
+ Utils.getColorAttrDefaultColor(context, id, defaultValue)
+ }
+ }
+
+ /**
+ * Returns a [Flow] that emits a [View] that is re-inflated as necessary to remain in sync with
+ * the device configuration.
+ *
+ * @see LayoutInflater.inflate
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun <T : View> inflateLayout(
+ @LayoutRes id: Int,
+ root: ViewGroup?,
+ attachToRoot: Boolean,
+ ): Flow<T> {
+ // TODO(b/305930747): This may lead to duplicate invocations if both flows emit, find a
+ // solution to only emit one event.
+ return merge(
+ configurationController.onThemeChanged,
+ configurationController.onDensityOrFontScaleChanged,
+ )
+ .emitOnStart()
+ .map { layoutInflater.inflate(id, root, attachToRoot) as T }
+ }
+}
+
+/**
+ * Perform an inflation right away, then re-inflate whenever the device configuration changes, and
+ * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
+ * [onInflate] when done.
+ *
+ * This never completes unless cancelled, it just suspends and waits for updates.
+ *
+ * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
+ *
+ * An example use-case of this is when a view needs to be re-inflated whenever a configuration
+ * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
+ * code in the parent view's binder would look like:
+ * ```
+ * parentView.repeatWhenAttached {
+ * configurationState
+ * .reinflateOnChange(
+ * R.layout.my_layout,
+ * parentView,
+ * attachToRoot = false,
+ * coroutineScope = lifecycleScope,
+ * configurationController.onThemeChanged,
+ * ) { view: ChildView ->
+ * ChildViewBinder.bind(view, childViewModel)
+ * }
+ * }
+ * ```
+ *
+ * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
+ * [DisposableHandle].
+ */
+suspend fun <T : View> ConfigurationState.reinflateAndBindLatest(
+ @LayoutRes resource: Int,
+ root: ViewGroup?,
+ attachToRoot: Boolean,
+ onInflate: (T) -> DisposableHandle?,
+) {
+ inflateLayout<T>(resource, root, attachToRoot).bindLatest(onInflate)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
new file mode 100644
index 0000000..b0e6931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.common.ui.data
+
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
+import dagger.Module
+
+@Module(includes = [ConfigurationRepositoryModule::class]) object CommonUiDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index b8de8d8..e449274 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -13,18 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.common.ui.data.repository
import android.content.Context
import android.content.res.Configuration
import android.view.DisplayInfo
+import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,7 +52,6 @@
fun getDimensionPixelSize(id: Int): Int
}
-@ExperimentalCoroutinesApi
@SysUISingleton
class ConfigurationRepositoryImpl
@Inject
@@ -119,7 +122,12 @@
return 1f
}
- override fun getDimensionPixelSize(id: Int): Int {
+ override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
}
+
+@Module
+interface ConfigurationRepositoryModule {
+ @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+}
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 323070a..0781451 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.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 @@
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 @@
)
},
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 c2d4d12..a742e8d 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 @@
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 @@
cancelScheduledLongPress()
if (
event.distanceMoved <= ViewConfiguration.getTouchSlop() &&
- event.gestureDuration < ViewConfiguration.getLongPressTimeout()
+ event.gestureDuration < longPressDuration()
) {
dispatchSingleTap()
}
@@ -103,7 +105,7 @@
y = y,
)
},
- ViewConfiguration.getLongPressTimeout().toLong(),
+ longPressDuration(),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
index 20b2494..f7b6b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
@@ -652,8 +652,7 @@
CrossFadeHelper.fadeOut(
mLayout,
mFadeOutDuration,
- /* delay= */ 0,
- /* endRunnable= */ null);
+ /* delay= */ 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f0d7592..04b2852d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,7 +41,7 @@
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
-import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.common.ui.data.CommonUiDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -170,7 +170,7 @@
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
- CommonRepositoryModule.class,
+ CommonUiDataLayerModule.class,
CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 4f16685..3e2ecee 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -47,6 +47,8 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -54,10 +56,7 @@
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
- /**
- * Provides a nullable set of displays. Updates when new displays have been added or removed but
- * not when a display's info has changed.
- */
+ /** Provides the current set of displays. */
val displays: Flow<Set<Display>>
/**
@@ -112,10 +111,6 @@
trySend(DisplayEvent.Changed(displayId))
}
}
- // Triggers an initial event when subscribed. This is needed to avoid getDisplays to
- // be called when this class is constructed, but only when someone subscribes to
- // this flow.
- trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY))
displayManager.registerDisplayListener(
callback,
backgroundHandler,
@@ -125,6 +120,7 @@
)
awaitClose { displayManager.unregisterDisplayListener(callback) }
}
+ .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
.flowOn(backgroundCoroutineDispatcher)
override val displayChangeEvent: Flow<Int> =
@@ -134,13 +130,9 @@
allDisplayEvents
.map { getDisplays() }
.flowOn(backgroundCoroutineDispatcher)
- .stateIn(
- applicationScope,
- started = SharingStarted.WhileSubscribed(),
- // To avoid getting displays on this object construction, they are get after the
- // first event. allDisplayEvents emits a changed event when we subscribe to it.
- initialValue = emptySet()
- )
+ .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+
+ override val displays: Flow<Set<Display>> = enabledDisplays
private fun getDisplays(): Set<Display> =
traceSection("DisplayRepository#getDisplays()") {
@@ -148,8 +140,6 @@
}
/** Propagate to the listeners only enabled displays */
- override val displays: Flow<Set<Display>> = enabledDisplays
-
private val enabledDisplayIds: Flow<Set<Int>> =
enabledDisplays
.map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
@@ -251,6 +241,7 @@
val id = pendingDisplayIds.maxOrNull() ?: return@map null
object : DisplayRepository.PendingDisplay {
override val id = id
+
override suspend fun enable() {
traceSection("DisplayRepository#enable($id)") {
if (DEBUG) {
@@ -303,8 +294,12 @@
private interface DisplayConnectionListener : DisplayListener {
override fun onDisplayConnected(id: Int) {}
+
override fun onDisplayDisconnected(id: Int) {}
+
override fun onDisplayAdded(id: Int) {}
+
override fun onDisplayRemoved(id: Int) {}
+
override fun onDisplayChanged(id: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 3f21533..87b0f01 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -19,9 +19,12 @@
import android.os.Bundle
import android.view.View
import android.widget.TextView
+import androidx.core.view.updatePadding
+import com.android.systemui.biometrics.Utils
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
import com.android.systemui.statusbar.policy.ConfigurationController
+import kotlin.math.max
/**
* Dialog used to decide what to do with a connected display.
@@ -58,5 +61,23 @@
onCancelMirroring.onClick(null)
}
}
+ setupInsets()
+ }
+
+ private fun setupInsets() {
+ // This avoids overlap between dialog content and navigation bars.
+ requireViewById<View>(R.id.cd_bottom_sheet).apply {
+ val navbarInsets = Utils.getNavbarInsets(context)
+ val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ updatePadding(bottom = max(navbarInsets.bottom, defaultDialogBottomInset))
+ }
+ }
+
+ override fun onConfigurationChanged() {
+ super.onConfigurationChanged()
+ setupInsets()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
index 83c239f..dd58604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -17,15 +17,19 @@
package com.android.systemui.flags
import android.util.Log
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.ConditionalRestarter.Condition
+import com.android.systemui.util.kotlin.UnflaggedApplication
+import com.android.systemui.util.kotlin.UnflaggedBackground
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/** Restarts the process after all passed in [Condition]s are true. */
@@ -35,11 +39,10 @@
private val systemExitRestarter: SystemExitRestarter,
private val conditions: Set<@JvmSuppressWildcards Condition>,
@Named(RESTART_DELAY) private val restartDelaySec: Long,
- @Application private val applicationScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @UnflaggedApplication private val applicationScope: CoroutineScope,
+ @UnflaggedBackground private val backgroundDispatcher: CoroutineContext,
) : Restarter {
- private var restartJob: Job? = null
private var pendingReason = ""
private var androidRestartRequested = false
@@ -57,17 +60,19 @@
private fun scheduleRestart(reason: String = "") {
pendingReason = if (reason.isEmpty()) pendingReason else reason
- if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
- if (restartJob == null) {
- restartJob =
- applicationScope.launch(backgroundDispatcher) {
+ applicationScope.launch(backgroundDispatcher) {
+ combine(conditions.map { condition -> condition.canRestartNow }) { it.all { it } }
+ // Once all conditions are met, delay.
+ .transformLatest { allConditionsMet ->
+ if (allConditionsMet) {
delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
- restartNow()
+ emit(Unit)
}
- }
- } else {
- restartJob?.cancel()
- restartJob = null
+ }
+ // Once we have successfully delayed _once_, continue to restart.
+ .first()
+
+ restartNow()
}
}
@@ -94,7 +99,7 @@
* multiple [Condition]s are being checked. If any one [Condition] returns false, all the
* [Condition]s will need to be rechecked on the next restart attempt.
*/
- fun canRestartNow(retryFn: () -> Unit): Boolean
+ val canRestartNow: Flow<Boolean>
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 10fac4d..ff1df36 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -87,11 +87,6 @@
@JvmField
val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor")
- // TODO(b/290787599): Tracking Bug
- @JvmField
- val NOTIFICATION_ICON_CONTAINER_REFACTOR =
- unreleasedFlag("notification_icon_container_refactor")
-
// TODO(b/288326013): Tracking Bug
@JvmField
val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
@@ -118,12 +113,12 @@
// 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 **
@@ -484,7 +479,7 @@
val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
// TODO(b/304506662): Tracking Bug
- val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true)
+ val MEDIA_DEVICE_NAME_FIX = releasedFlag("media_device_name_fix")
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
@@ -623,7 +618,7 @@
/** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
@JvmField
- val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)
+ val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot")
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
@@ -727,7 +722,7 @@
@JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator")
// TODO(b/277192623): Tracking Bug
- @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = true)
+ @JvmField val KEYBOARD_EDUCATION = releasedFlag("keyboard_education")
// TODO(b/277201412): Tracking Bug
@JvmField
@@ -773,7 +768,8 @@
@JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
// TODO(b/302087895): Tracking Bug
- @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
+ @JvmField val CALL_LAYOUT_ASYNC_SET_DATA =
+ unreleasedFlag("call_layout_async_set_data", teamfood = true)
// TODO(b/302144438): Tracking Bug
@JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
new file mode 100644
index 0000000..f5b30cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class NotOccludedCondition
+@Inject
+constructor(
+ private val keyguardTransitionInteractorLazy: Lazy<KeyguardTransitionInteractor>,
+) : ConditionalRestarter.Condition {
+
+ override val canRestartNow: Flow<Boolean>
+ get() {
+ return keyguardTransitionInteractorLazy
+ .get()
+ .transitionValue(KeyguardState.OCCLUDED)
+ .map { it == 0f }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
index 3120638..dc08570 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -16,34 +16,34 @@
package com.android.systemui.flags
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.BatteryController
+import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
/** Returns true when the device is plugged in. */
class PluggedInCondition
@Inject
constructor(
- private val batteryController: BatteryController,
+ private val batteryControllerLazy: Lazy<BatteryController>,
) : ConditionalRestarter.Condition {
- var listenersAdded = false
- var retryFn: (() -> Unit)? = null
-
- val batteryCallback =
- object : BatteryController.BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- retryFn?.invoke()
+ override val canRestartNow = conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
}
- }
+ batteryControllerLazy.get().addCallback(batteryCallback)
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- batteryController.addCallback(batteryCallback)
- }
+ trySend(batteryControllerLazy.get().isPluggedIn)
- this.retryFn = retryFn
-
- return batteryController.isPluggedIn
+ awaitClose { batteryControllerLazy.get().removeCallback(batteryCallback) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
index 49e61af..3c9bc36 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -16,34 +16,19 @@
package com.android.systemui.flags
-import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
class ScreenIdleCondition
@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
-) : ConditionalRestarter.Condition {
+constructor(private val powerInteractorLazy: Lazy<PowerInteractor>) :
+ ConditionalRestarter.Condition {
- var listenersAdded = false
- var retryFn: (() -> Unit)? = null
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- retryFn?.invoke()
- }
+ override val canRestartNow: Flow<Boolean>
+ get() {
+ return powerInteractorLazy.get().isAsleep
}
-
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
- }
-
- this.retryFn = retryFn
-
- return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index bc07139..6f491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,9 +36,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.util.TraceUtils.Companion.runBlocking
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
class CustomizationProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -132,7 +132,7 @@
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { insertSelection(values) }
+ return runBlocking("$TAG#insert", mainDispatcher) { insertSelection(values) }
}
override fun query(
@@ -142,7 +142,7 @@
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
- return runBlocking(mainDispatcher) {
+ return runBlocking("$TAG#query", mainDispatcher) {
when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
MATCH_CODE_ALL_SLOTS -> querySlots()
@@ -172,7 +172,7 @@
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) }
+ return runBlocking("$TAG#delete", mainDispatcher) { deleteSelection(uri, selectionArgs) }
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 119ade4..c56dfde 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.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 @@
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 @@
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/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 3cdff76..6a0d595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,10 @@
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -78,10 +82,6 @@
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
/**
* API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -368,10 +368,12 @@
return arrayOf(
Pair(
and(
- displayStateInteractor.isDefaultDisplayOff,
- keyguardTransitionInteractor.isFinishedInStateWhere(
- KeyguardState::deviceIsAwakeInState),
- ).isFalse(),
+ displayStateInteractor.isDefaultDisplayOff,
+ keyguardTransitionInteractor.isFinishedInStateWhere(
+ KeyguardState::deviceIsAwakeInState
+ ),
+ )
+ .isFalse(),
// this can happen if an app is requesting for screen off, the display can
// turn off without wakefulness.isStartingToSleepOrAsleep calls
"displayIsNotOffWhileFullyTransitionedToAwake",
@@ -381,10 +383,7 @@
"isFaceAuthEnrolledAndEnabled"
),
Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
- Pair(
- powerInteractor.isAsleep.isFalse(),
- "deviceNotAsleep"
- ),
+ Pair(powerInteractor.isAsleep.isFalse(), "deviceNotAsleep"),
Pair(
keyguardInteractor.isSecureCameraActive
.isFalse()
@@ -430,10 +429,15 @@
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedFaceAuthenticationStatus()
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
- onFaceAuthRequestCompleted()
+ if (!_isLockedOut.value) {
+ // onAuthenticationError gets invoked before onAuthenticationFailed when the
+ // last auth attempt locks out face authentication.
+ // Skip updating the authentication status in such a scenario.
+ _authenticationStatus.value = FailedFaceAuthenticationStatus()
+ onFaceAuthRequestCompleted()
+ }
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
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 2dc4908..4d26466 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 @@
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 @@
}
.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/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index d44a9d8..95ac0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -31,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.TraceUtils.Companion.launch
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -43,7 +44,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -136,7 +136,7 @@
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToDreaming") {
keyguardInteractor.isAbleToDream
.sample(
combine(
@@ -169,7 +169,7 @@
}
private fun listenForLockscreenToPrimaryBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -184,7 +184,7 @@
}
private fun listenForLockscreenToAlternateBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
keyguardInteractor.alternateBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -202,7 +202,7 @@
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.shadeModel
.sample(
combine(
@@ -287,7 +287,7 @@
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGone") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -304,7 +304,7 @@
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGoneDragging") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -317,7 +317,7 @@
}
private fun listenForLockscreenToOccluded() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToOccluded") {
keyguardInteractor.isKeyguardOccluded
.sample(transitionInteractor.startedKeyguardState, ::Pair)
.collect { (isOccluded, keyguardState) ->
@@ -329,7 +329,7 @@
}
private fun listenForLockscreenToAodOrDozing() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
powerInteractor.isAsleep
.sample(
combine(
@@ -375,6 +375,7 @@
}
companion object {
+ const val TAG = "FromLockscreenTransitionInteractor"
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
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 b953b48..eaec0d4 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.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 @@
private val powerInteractor: PowerInteractor,
featureFlags: FeatureFlags,
sceneContainerFlags: SceneContainerFlags,
- deviceEntryRepository: DeviceEntryRepository,
bouncerRepository: KeyguardBouncerRepository,
configurationRepository: ConfigurationRepository,
shadeRepository: ShadeRepository,
@@ -160,7 +158,7 @@
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 122c4c4..55b420b0 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.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 @@
}
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 @@
(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/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index de791aa..fe9370f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -23,7 +23,6 @@
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -44,11 +43,12 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.TraceUtils.Companion.traceAsync
+import com.android.systemui.util.TraceUtils.Companion.withContext
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +59,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -417,10 +416,8 @@
}
private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
- withContext(backgroundDispatcher) {
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
- }
+ withContext("$TAG#isFeatureDisabledByDevicePolicy", backgroundDispatcher) {
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
}
companion object {
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 0000000..e82ea7f
--- /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 0000000..c9e3954
--- /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 d8e4396..21eba56 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.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 @@
@Inject
constructor(
defaultIndicationAreaSection: DefaultIndicationAreaSection,
- defaultLockIconSection: DefaultLockIconSection,
+ defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
defaultShortcutsSection: DefaultShortcutsSection,
defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
@@ -61,7 +61,7 @@
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 ce76f56..f8dd7c1 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.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 @@
@Inject
constructor(
defaultIndicationAreaSection: DefaultIndicationAreaSection,
- defaultLockIconSection: DefaultLockIconSection,
+ defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
@@ -54,7 +54,7 @@
override val sections =
setOf(
defaultIndicationAreaSection,
- defaultLockIconSection,
+ defaultDeviceEntryIconSection,
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 669db34..b7fe960 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -26,23 +26,40 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
class AodNotificationIconsSection
@Inject
constructor(
private val context: Context,
- private val featureFlags: FeatureFlags,
+ private val configurationState: ConfigurationState,
+ private val configurationController: ConfigurationController,
+ private val dozeParameters: DozeParameters,
+ private val featureFlags: FeatureFlagsClassic,
+ private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+ private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val notificationPanelView: NotificationPanelView,
private val notificationIconAreaController: NotificationIconAreaController,
+ private val screenOffAnimationController: ScreenOffAnimationController,
) : KeyguardSection() {
+
+ private var nicBindingDisposable: DisposableHandle? = null
private val nicId = R.id.aod_notification_icon_container
private lateinit var nic: NotificationIconContainer
@@ -70,7 +87,23 @@
return
}
- notificationIconAreaController.setupAodIcons(nic)
+ if (NotificationIconContainerRefactor.isEnabled) {
+ nic.setOnLockScreen(true)
+ nicBindingDisposable?.dispose()
+ nicBindingDisposable =
+ NotificationIconContainerViewBinder.bind(
+ nic,
+ nicAodViewModel,
+ configurationState,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ nicAodIconViewStore,
+ )
+ } else {
+ notificationIconAreaController.setupAodIcons(nic)
+ }
}
override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -102,5 +135,6 @@
override fun removeViews(constraintLayout: ConstraintLayout) {
constraintLayout.removeView(nicId)
+ nicBindingDisposable?.dispose()
}
}
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
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
index f4bc713..62c5988 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.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 @@
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 @@
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 @@
}
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 0000000..842dde3
--- /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/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
index 0aa6b0b..c73afe8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.base.interactor
+package com.android.systemui.log.dagger
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+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 fd6b3f1..e768f16 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -91,7 +91,7 @@
@SysUISingleton
@NotifInflationLog
public static LogBuffer provideNotifInflationLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifInflationLog", 100);
+ return factory.create("NotifInflationLog", 250);
}
/** Provides a logging buffer for notification interruption calculations. */
@@ -552,4 +552,12 @@
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/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 565bf24..baa07c1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -95,7 +95,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -114,7 +114,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -133,7 +133,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -152,7 +152,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -176,7 +176,7 @@
)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+ return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
if (prevVal != newVal) {
// TODO(b/267761156): Can we log list changes without using toString?
tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
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 1db31ae..2034d97 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index d8cab6a..98a3896 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -16,14 +16,12 @@
package com.android.systemui.mediaprojection
import android.media.projection.IMediaProjectionManager
-import android.os.Process
import android.os.RemoteException
import android.util.Log
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED as METRICS_STATE_PERMISSION_REQUEST_DISPLAYED
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -53,48 +51,42 @@
}
}
- fun notifyPermissionRequestDisplayed() {
- notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN)
- }
-
/**
- * Request to log that the permission request moved to the given state.
+ * Request to log that the permission request was displayed.
*
- * Should not be used for the initialization state, since that should use {@link
- * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
- * sessionCreationSource.
+ * @param hostUid The UID of the package that initiates MediaProjection.
*/
- fun notifyPermissionProgress(state: Int) {
- // TODO validate state is valid
- notifyToServer(state, SessionCreationSource.UNKNOWN)
- }
-
- /**
- * Notifies system server that we are handling a particular state during the consent flow.
- *
- * Only used for emitting atoms.
- *
- * @param state The state that SystemUI is handling during the consent flow. Must be a valid
- * state defined in the MediaProjectionState enum.
- * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
- * Indicates the entry point for requesting the permission. Must be a valid state defined in
- * the SessionCreationSource enum.
- */
- private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
- Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
+ fun notifyPermissionRequestDisplayed(hostUid: Int) {
try {
- service.notifyPermissionRequestStateChange(
- Process.myUid(),
- state,
- sessionCreationSource.toMetricsConstant()
- )
+ service.notifyPermissionRequestDisplayed(hostUid)
} catch (e: RemoteException) {
- Log.e(
- TAG,
- "Error notifying server of permission flow state $state from source " +
- "$sessionCreationSource",
- e
- )
+ Log.e(TAG, "Error notifying server of projection displayed", e)
+ }
+ }
+
+ /**
+ * Request to log that the permission request was cancelled.
+ *
+ * @param hostUid The UID of the package that initiates MediaProjection.
+ */
+ fun notifyProjectionRequestCancelled(hostUid: Int) {
+ try {
+ service.notifyPermissionRequestCancelled(hostUid)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of projection cancelled", e)
+ }
+ }
+
+ /**
+ * Request to log that the app selector was displayed.
+ *
+ * @param hostUid The UID of the package that initiates MediaProjection.
+ */
+ fun notifyAppSelectorDisplayed(hostUid: Int) {
+ try {
+ service.notifyAppSelectorDisplayed(hostUid)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of app selector displayed", e)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 0bbcfd9..50e9e751 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -92,6 +92,7 @@
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
+ hostUid = hostUid,
callingPackage = callingPackage,
view = this,
resultHandler = this,
@@ -209,6 +210,10 @@
reviewGrantedConsentRequired,
/* projection= */ null
)
+ if (isFinishing) {
+ // Only log dismissed when actually finishing, and not when changing configuration.
+ controller.onSelectorDismissed()
+ }
}
activityLauncher.destroy()
controller.destroy()
@@ -305,6 +310,17 @@
)
}
+ private val hostUid: Int
+ get() {
+ if (!intent.hasExtra(EXTRA_HOST_APP_UID)) {
+ error(
+ "MediaProjectionAppSelectorActivity should be provided with " +
+ "$EXTRA_HOST_APP_UID extra"
+ )
+ }
+ return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1)
+ }
+
companion object {
const val TAG = "MediaProjectionAppSelectorActivity"
@@ -315,8 +331,16 @@
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
- /** UID of the app that originally launched the media projection flow (host app user) */
+ /**
+ * User on the device that launched the media projection flow. (Primary, Secondary, Guest,
+ * Work, etc)
+ */
const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
+ /**
+ * The kernel user-ID that has been assigned to the app that originally launched the media
+ * projection flow.
+ */
+ const val EXTRA_HOST_APP_UID = "launched_from_host_uid"
const val KEY_CAPTURE_TARGET = "capture_region"
/** Set up intent for the [ChooserActivity] */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 8c6f307..d247122 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -57,6 +57,8 @@
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUid
+
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
@Module(
@@ -143,6 +145,7 @@
/** Create a factory to inject the activity into the graph */
fun create(
@BindsInstance @HostUserHandle hostUserHandle: UserHandle,
+ @BindsInstance @HostUid hostUid: Int,
@BindsInstance @MediaProjectionAppSelector callingPackage: String?,
@BindsInstance view: MediaProjectionAppSelectorView,
@BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 69132d3..575e198 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,7 +18,6 @@
import android.content.ComponentName
import android.os.UserHandle
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
@@ -47,13 +46,14 @@
private val thumbnailLoader: RecentTaskThumbnailLoader,
@MediaProjectionAppSelector private val isFirstStart: Boolean,
private val logger: MediaProjectionMetricsLogger,
+ @HostUid private val hostUid: Int,
) {
fun init() {
// Only log during the first start of the app selector.
// Don't log when the app selector restarts due to a config change.
if (isFirstStart) {
- logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ logger.notifyAppSelectorDisplayed(hostUid)
}
scope.launch {
@@ -75,6 +75,10 @@
scope.cancel()
}
+ fun onSelectorDismissed() {
+ logger.notifyProjectionRequestCancelled(hostUid)
+ }
+
private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) {
coroutineScope {
val thumbnails: List<Deferred<ThumbnailData?>> =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
index 8b437c3..eea369f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
@@ -32,6 +32,7 @@
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -40,16 +41,31 @@
context: Context,
private val screenShareOptions: List<ScreenShareOption>,
private val appName: String?,
+ private val hostUid: Int,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
@DrawableRes private val dialogIconDrawable: Int? = null,
- @ColorRes private val dialogIconTint: Int? = null
+ @ColorRes private val dialogIconTint: Int? = null,
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
private lateinit var dialogTitle: TextView
private lateinit var startButton: TextView
private lateinit var cancelButton: TextView
private lateinit var warning: TextView
private lateinit var screenShareModeSpinner: Spinner
+ private var hasCancelBeenLogged: Boolean = false
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
+ override fun dismiss() {
+ super.dismiss()
+
+ // Dismiss can be called multiple times and we only want to log once.
+ if (hasCancelBeenLogged) {
+ return
+ }
+
+ mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+ hasCancelBeenLogged = true
+ }
+
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 922f7e0..eacfa57 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -222,13 +222,19 @@
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
- mDialog = new MediaProjectionPermissionDialog(dialogContext, getMediaProjectionConfig(),
+ mDialog = new MediaProjectionPermissionDialog(
+ dialogContext,
+ getMediaProjectionConfig(),
() -> {
MediaProjectionPermissionDialog dialog =
(MediaProjectionPermissionDialog) mDialog;
ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
grantMediaProjectionPermission(selectedOption.getMode());
- }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
+ },
+ () -> finish(RECORD_CANCEL, /* projection= */ null),
+ appName,
+ mUid,
+ mMediaProjectionMetricsLogger);
} else {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
R.style.Theme_SystemUI_Dialog)
@@ -252,7 +258,7 @@
mDialog.show();
if (savedInstanceState == null) {
- mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
}
}
@@ -330,6 +336,9 @@
projection.asBinder());
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
getHostUserHandle());
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+ getLaunchedFromUid());
intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index b9bafd4..cff22b0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.media.projection.MediaProjectionConfig
import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
/** Dialog to select screen recording options */
@@ -26,12 +27,16 @@
mediaProjectionConfig: MediaProjectionConfig?,
private val onStartRecordingClicked: Runnable,
private val onCancelClicked: Runnable,
- private val appName: String?
+ private val appName: String?,
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseScreenSharePermissionDialog(
context,
createOptionList(context, appName, mediaProjectionConfig),
- appName
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger
) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 38204ab..a6c6233 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.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 @@
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 68bf88b..3b3844a 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 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 @@
}
@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 4aad6a0..fab7e95 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 @@
private CommandQueue mCommandQueue;
private View mRootView;
+ private View mFooterActionsView;
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -285,6 +286,7 @@
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 @@
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 @@
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 @@
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 @@
@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 fc2f5f9..11db69b 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 @@
@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 a65967a..bd4c6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -31,7 +31,7 @@
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.viewmodel.QSTileViewModel;
+import com.android.systemui.qs.tiles.di.QSTilesModule;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
@@ -60,20 +60,18 @@
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 */
- @Multibinds
- Map<String, QSTileViewModel> tileViewModelMap();
-
@Provides
@SysUISingleton
static AutoTileManager provideAutoTileManager(
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 051eeb0..bd13d06 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 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 @@
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 d09b210..0995dd4 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.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.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 @@
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 @@
// 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 eff3e76..aff4a67 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 @@
*/
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 @@
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/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index e27a59c..f37f58d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -47,6 +47,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +72,7 @@
private final FeatureFlags mFlags;
private final PanelInteractor mPanelInteractor;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final UserContextProvider mUserContextProvider;
private long mMillisUntilFinished = 0;
@@ -91,7 +93,8 @@
KeyguardStateController keyguardStateController,
DialogLaunchAnimator dialogLaunchAnimator,
PanelInteractor panelInteractor,
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ UserContextProvider userContextProvider
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -103,6 +106,7 @@
mDialogLaunchAnimator = dialogLaunchAnimator;
mPanelInteractor = panelInteractor;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mUserContextProvider = userContextProvider;
}
@Override
@@ -195,7 +199,8 @@
dialog.show();
}
- mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+ int uid = mUserContextProvider.getUserContext().getUserId();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
return false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 9d10072..905d8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.base.actions
+import android.app.PendingIntent
import android.content.Intent
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
@@ -29,7 +30,7 @@
* dismissing and tile from-view animations.
*/
@SysUISingleton
-class QSTileIntentUserActionHandler
+class QSTileIntentUserInputHandler
@Inject
constructor(private val activityStarter: ActivityStarter) {
@@ -43,4 +44,19 @@
}
activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
}
+
+ // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
+ fun handle(view: View?, pendingIntent: PendingIntent) {
+ if (!pendingIntent.isActivity) {
+ return
+ }
+ val animationController: ActivityLaunchAnimator.Controller? =
+ view?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ )
+ }
+ activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
new file mode 100644
index 0000000..4f25d3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.interactor
+
+/** Event that triggers data update */
+sealed interface DataUpdateTrigger {
+ /**
+ * State update is requested in a response to a user action.
+ * - [action] is the action that happened
+ * - [tileData] is the data state of the tile when that action took place
+ */
+ class UserInput<T>(val input: QSTileInput<T>) : DataUpdateTrigger
+
+ /** Force update current state. This is passed when the view needs a new state to show */
+ data object ForceUpdate : DataUpdateTrigger
+
+ /** The data is requested loaded for the first time */
+ data object InitialRequest : DataUpdateTrigger
+}
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 056f967..d1f8945 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 @@
/**
* 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 @@
* 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 @@
@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 7a22e3c..9752fea 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,7 +16,7 @@
package com.android.systemui.qs.tiles.base.interactor
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import android.os.UserHandle
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -29,14 +29,19 @@
interface QSTileDataInteractor<DATA_TYPE> {
/**
- * Returns the data to be mapped to [QSTileState]. Make sure to start the flow [Flow.onStart]
- * with the current state to update the tile as soon as possible.
+ * Returns a data flow scoped to the user. This means the subscription will live when the tile
+ * 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(qsTileDataRequest: QSTileDataRequest): Flow<DATA_TYPE>
+ fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
/**
- * Returns tile availability - whether this device currently supports this tile. Make sure to
- * start the flow [Flow.onStart] with the current state to update the tile as soon as possible.
+ * Returns tile availability - whether this device currently supports this tile.
+ *
+ * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
+ * as possible.
*/
- fun availability(): Flow<Boolean>
+ fun availability(user: UserHandle): Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
index 0aa6b0b..77ff609 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
@@ -16,7 +16,12 @@
package com.android.systemui.qs.tiles.base.interactor
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+
+/** @see QSTileUserActionInteractor.handleInput */
+data class QSTileInput<T>(
+ val user: UserHandle,
+ val action: QSTileUserAction,
+ val data: T,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 14fc639..09d7a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -17,13 +17,14 @@
package com.android.systemui.qs.tiles.base.interactor
import android.annotation.WorkerThread
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
interface QSTileUserActionInteractor<DATA_TYPE> {
-
/**
- * Processes user input based on [userAction] and [currentData]. It's safe to run long running
- * computations inside this function in this.
+ * Processes user input based on [QSTileInput.userId], [QSTileInput.action], and
+ * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to
+ * [QSTileDataInteractor] to get [QSTileInput.data].
+ *
+ * It's safe to run long running computations inside this function in this.
*/
- @WorkerThread suspend fun handleInput(userAction: QSTileUserAction, currentData: DATA_TYPE)
+ @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
deleted file mode 100644
index ffe38dd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.base.interactor
-
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-
-sealed interface StateUpdateTrigger {
- class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) :
- StateUpdateTrigger
- data object ForceUpdate : StateUpdateTrigger
- data object InitialRequest : StateUpdateTrigger
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index f8de365..4dc1c82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -24,7 +24,7 @@
import com.android.systemui.log.dagger.QSTilesLogBuffers
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.StatusBarState
@@ -128,10 +128,21 @@
)
}
+ fun logForceUpdate(tileSpec: TileSpec) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data force update" })
+ }
+
+ fun logInitialRequest(tileSpec: TileSpec) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data initial update" })
+ }
+
/** Tracks state changes based on the data and trigger event. */
fun <T> logStateUpdate(
tileSpec: TileSpec,
- trigger: StateUpdateTrigger,
tileState: QSTileState,
data: T,
) {
@@ -141,11 +152,10 @@
tileSpec.getLogTag(),
LogLevel.DEBUG,
{
- str1 = trigger.toLogString()
- str2 = tileState.toLogString()
- str3 = data.toString().take(DATA_MAX_LENGTH)
+ str1 = tileState.toLogString()
+ str2 = data.toString().take(DATA_MAX_LENGTH)
},
- { "tile state update: trigger=$str1, state=$str2, data=$str3" }
+ { "tile state update: state=$str1, data=$str2" }
)
}
@@ -162,11 +172,11 @@
}
}
- private fun StateUpdateTrigger.toLogString(): String =
+ private fun DataUpdateTrigger.toLogString(): String =
when (this) {
- is StateUpdateTrigger.ForceUpdate -> "force"
- is StateUpdateTrigger.InitialRequest -> "init"
- is StateUpdateTrigger.UserAction<*> -> action.toLogString()
+ is DataUpdateTrigger.ForceUpdate -> "force"
+ is DataUpdateTrigger.InitialRequest -> "init"
+ is DataUpdateTrigger.UserInput<*> -> input.action.toLogString()
}
private fun QSTileUserAction.toLogString(): String =
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/BaseQSTileViewModel.kt
deleted file mode 100644
index 2114751..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.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 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.QSTileDataRequest
-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.interactor.StateUpdateTrigger
-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
-import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
-import com.android.systemui.util.kotlin.sample
-import com.android.systemui.util.kotlin.throttle
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
-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.
- *
- * Inject [BaseQSTileViewModel.Factory] to create a new instance of this 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>,
- private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
- private val falsingManager: FalsingManager,
- private val qsTileAnalytics: QSTileAnalytics,
- private val qsTileLogger: QSTileLogger,
- private val backgroundDispatcher: CoroutineDispatcher,
- private val tileScope: CoroutineScope,
-) : QSTileViewModel {
-
- @AssistedInject
- constructor(
- @Assisted config: QSTileConfig,
- @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
- @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
- @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
- disabledByPolicyInteractor: DisabledByPolicyInteractor,
- falsingManager: FalsingManager,
- qsTileAnalytics: QSTileAnalytics,
- qsTileLogger: QSTileLogger,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ) : this(
- config,
- userActionInteractor,
- tileDataInteractor,
- mapper,
- disabledByPolicyInteractor,
- falsingManager,
- qsTileAnalytics,
- qsTileLogger,
- backgroundDispatcher,
- CoroutineScope(SupervisorJob())
- )
-
- private val userInputs: MutableSharedFlow<QSTileUserAction> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- private val userIds: MutableSharedFlow<Int> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- private val forceUpdates: MutableSharedFlow<Unit> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- private val spec
- get() = config.tileSpec
-
- private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>>
-
- override lateinit var state: SharedFlow<QSTileState>
- override val isAvailable: StateFlow<Boolean> =
- tileDataInteractor
- .availability()
- .flowOn(backgroundDispatcher)
- .stateIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- 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)
- }
-
- @CallSuper
- override fun onActionPerformed(userAction: QSTileUserAction) {
- Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
-
- qsTileLogger.logUserAction(
- userAction,
- spec,
- tileData.replayCache.isNotEmpty(),
- state.replayCache.isNotEmpty()
- )
- userInputs.tryEmit(userAction)
- }
-
- @CallSuper
- override fun onLifecycle(lifecycle: QSTileLifecycle) {
- when (lifecycle) {
- QSTileLifecycle.ALIVE -> {
- Preconditions.checkState(currentLifeState == QSTileLifecycle.DEAD)
- tileData = createTileDataFlow()
- state =
- tileData
- // TODO(b/299908705): log data and corresponding tile state
- .map { dataWithTrigger ->
- mapper.map(config, dataWithTrigger.data).also { state ->
- qsTileLogger.logStateUpdate(
- spec,
- dataWithTrigger.trigger,
- state,
- dataWithTrigger.data
- )
- }
- }
- .flowOn(backgroundDispatcher)
- .shareIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- replay = 1,
- )
- }
- QSTileLifecycle.DEAD -> {
- Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
- tileScope.coroutineContext.cancelChildren()
- }
- }
- currentLifeState = lifecycle
- }
-
- private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
- userIds
- .flatMapLatest { userId ->
- merge(
- userInputFlow(userId),
- forceUpdates.map { StateUpdateTrigger.ForceUpdate },
- )
- .onStart { emit(StateUpdateTrigger.InitialRequest) }
- .map { trigger -> QSTileDataRequest(userId, trigger) }
- }
- .flatMapLatest { request ->
- // 1) get an updated data source
- // 2) process user input, possibly triggering new data to be emitted
- // This handles the case when the data isn't buffered in the interactor
- // TODO(b/299908705): Log events that trigger data flow to update
- val dataFlow = tileDataInteractor.tileData(request)
- if (request.trigger is StateUpdateTrigger.UserAction<*>) {
- userActionInteractor.handleInput(
- request.trigger.action,
- request.trigger.tileData as DATA_TYPE,
- )
- }
- dataFlow.map { DataWithTrigger(it, request.trigger) }
- }
- .flowOn(backgroundDispatcher)
- .shareIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- replay = 1, // we only care about the most recent value
- )
-
- private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
- data class StateWithData<T>(val state: QSTileState, val data: T)
-
- return when (config.policy) {
- is QSTilePolicy.NoRestrictions -> userInputs
- is QSTilePolicy.Restricted ->
- userInputs.filter { action ->
- val result =
- disabledByPolicyInteractor.isDisabled(
- userId,
- config.policy.userRestriction
- )
- !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
- if (isDisabled) {
- qsTileLogger.logUserActionRejectedByPolicy(action, spec)
- }
- }
- }
- }
- .filter { action ->
- val isFalseAction =
- when (action) {
- is QSTileUserAction.Click ->
- falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
- is QSTileUserAction.LongClick ->
- falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
- }
- if (isFalseAction) {
- qsTileLogger.logUserActionRejectedByFalsing(action, spec)
- }
- !isFalseAction
- }
- .throttle(500)
- // Skip the input until there is some data
- .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
- input,
- stateWithData ->
- StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also {
- qsTileLogger.logUserActionPipeline(
- spec,
- it.action,
- stateWithData.state,
- stateWithData.data
- )
- qsTileAnalytics.trackUserAction(config, it.action)
- }
- }
- }
-
- private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
-
- 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/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
new file mode 100644
index 0000000..736f7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.pipeline.shared.TileSpec
+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.QSTileConfigProvider
+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 qsTileConfigProvider: QSTileConfigProvider,
+ 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(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> =
+ QSTileViewModelImpl(
+ qsTileConfigProvider.getConfig(tileSpec.spec),
+ 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 qsTileConfigProvider: QSTileConfigProvider,
+ private val systemClock: SystemClock,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ ) : QSTileViewModelFactory<T> {
+
+ /**
+ * @param tileSpec of the created 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(
+ tileSpec: TileSpec,
+ userActionInteractor: QSTileUserActionInteractor<T>,
+ tileDataInteractor: QSTileDataInteractor<T>,
+ mapper: QSTileDataToStateMapper<T>,
+ ): QSTileViewModelImpl<T> =
+ QSTileViewModelImpl(
+ qsTileConfigProvider.getConfig(tileSpec.spec),
+ { userActionInteractor },
+ { tileDataInteractor },
+ { mapper },
+ disabledByPolicyInteractor,
+ userRepository,
+ falsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ systemClock,
+ backgroundDispatcher,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
new file mode 100644
index 0000000..0bee48f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -0,0 +1,227 @@
+/*
+ * 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 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
+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.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.QSTilePolicy
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+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 kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.cancellable
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+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 its [state] is listened.
+ *
+ * Don't use this constructor directly. Instead, inject [QSTileViewModelFactory] to create a new
+ * instance of this class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelImpl<DATA_TYPE>(
+ override val config: 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,
+ private val qsTileAnalytics: QSTileAnalytics,
+ private val qsTileLogger: QSTileLogger,
+ private val systemClock: SystemClock,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ private val tileScope: CoroutineScope = CoroutineScope(SupervisorJob()),
+) : QSTileViewModel {
+
+ 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> =
+ MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private val spec
+ get() = config.tileSpec
+
+ private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow()
+
+ 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> =
+ users
+ .flatMapLatest { tileDataInteractor().availability(it) }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ tileScope,
+ SharingStarted.WhileSubscribed(),
+ true,
+ )
+
+ override fun forceUpdate() {
+ forceUpdates.tryEmit(Unit)
+ }
+
+ override fun onUserChanged(user: UserHandle) {
+ users.tryEmit(user)
+ }
+
+ override fun onActionPerformed(userAction: QSTileUserAction) {
+ qsTileLogger.logUserAction(
+ userAction,
+ spec,
+ tileData.replayCache.isNotEmpty(),
+ state.replayCache.isNotEmpty()
+ )
+ userInputs.tryEmit(userAction)
+ }
+
+ override fun destroy() {
+ tileScope.cancel()
+ }
+
+ private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+ users
+ .flatMapLatest { user ->
+ val updateTriggers =
+ merge(
+ userInputFlow(user),
+ forceUpdates
+ .map { DataUpdateTrigger.ForceUpdate }
+ .onEach { qsTileLogger.logForceUpdate(spec) },
+ )
+ .onStart {
+ emit(DataUpdateTrigger.InitialRequest)
+ qsTileLogger.logInitialRequest(spec)
+ }
+ tileDataInteractor()
+ .tileData(user, updateTriggers)
+ .cancellable()
+ .flowOn(backgroundDispatcher)
+ }
+ .shareIn(
+ tileScope,
+ SharingStarted.WhileSubscribed(),
+ replay = 1, // we only care about the most recent value
+ )
+
+ /**
+ * Creates a user input flow which:
+ * - filters false inputs with [falsingManager]
+ * - takes care of a tile being disable by policy using [disabledByPolicyInteractor]
+ * - notifies [userActionInteractor] about the action
+ * - logs it accordingly using [qsTileLogger] and [qsTileAnalytics]
+ *
+ * Subscribing to the result flow twice will result in doubling all actions, logs and analytics.
+ */
+ private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> {
+ return userInputs
+ .filterFalseActions()
+ .filterByPolicy(user)
+ .throttle(CLICK_THROTTLE_DURATION, systemClock)
+ // Skip the input until there is some data
+ .mapNotNull { action ->
+ val state: QSTileState = state.replayCache.lastOrNull() ?: return@mapNotNull null
+ val data: DATA_TYPE = tileData.replayCache.lastOrNull() ?: return@mapNotNull null
+ qsTileLogger.logUserActionPipeline(spec, action, state, data)
+ qsTileAnalytics.trackUserAction(config, action)
+
+ DataUpdateTrigger.UserInput(QSTileInput(user, action, data))
+ }
+ .onEach { userActionInteractor().handleInput(it.input) }
+ .flowOn(backgroundDispatcher)
+ }
+
+ 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> =
+ filter { action ->
+ val isFalseAction =
+ when (action) {
+ is QSTileUserAction.Click ->
+ falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ is QSTileUserAction.LongClick ->
+ falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ }
+ if (isFalseAction) {
+ qsTileLogger.logUserActionRejectedByFalsing(action, spec)
+ }
+ !isFalseAction
+ }
+
+ private companion object {
+ const val CLICK_THROTTLE_DURATION = 200L
+ }
+}
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 d0809c5..7d7af64 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,7 @@
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.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
import javax.inject.Inject
@@ -30,15 +30,22 @@
class NewQSTileFactory
@Inject
constructor(
+ qsTileConfigProvider: QSTileConfigProvider,
private val adapterFactory: QSTileViewModelAdapter.Factory,
private val tileMap:
Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
) : QSFactory {
+ init {
+ for (viewModelTileSpec in tileMap.keys) {
+ // throws an exception when there is no config for a tileSpec of an injected viewModel
+ qsTileConfigProvider.getConfig(viewModelTileSpec)
+ }
+ }
+
override fun createTile(tileSpec: String): QSTile? =
tileMap[tileSpec]?.let {
val tile = it.get()
- tile.onLifecycle(QSTileLifecycle.ALIVE)
adapterFactory.create(tile)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
new file mode 100644
index 0000000..32522ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.Multibinds
+
+/** Module listing subcomponents */
+@Module(
+ subcomponents =
+ [
+ CustomTileComponent::class,
+ ]
+)
+interface QSTilesModule {
+
+ /**
+ * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty
+ */
+ @Multibinds fun tileViewModelConfigs(): Map<String, QSTileConfig>
+
+ /**
+ * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty
+ */
+ @Multibinds fun tileViewModelMap(): Map<String, QSTileViewModel>
+
+ @Binds fun bindQSTileConfigProvider(impl: QSTileConfigProviderImpl): QSTileConfigProvider
+}
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 8957fc3..9c63a30 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 @@
@Inject
constructor(
private val localBluetoothManager: LocalBluetoothManager?,
+ private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
) {
@@ -47,6 +49,10 @@
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 @@
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 80af76d..7b4b557 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.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 @@
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) {
@@ -75,7 +78,10 @@
super.onCreate(savedInstanceState)
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
- setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null))
+ LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null).apply {
+ accessibilityPaneTitle = context.getText(R.string.accessibility_desc_quick_settings)
+ setContentView(this)
+ }
toggleView = requireViewById(R.id.bluetooth_toggle)
subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
@@ -102,21 +108,32 @@
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)
}
}
internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) {
- toggleView.isChecked = isEnabled
+ toggleView.apply {
+ isChecked = isEnabled
+ setEnabled(true)
+ alpha = ENABLED_ALPHA
+ }
subtitleTextView.text = context.getString(subtitleResId)
}
private fun setupToggle() {
toggleView.isChecked = bluetoothToggleInitialValue
- toggleView.setOnCheckedChangeListener { _, isChecked ->
+ toggleView.setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothStateToggle.value = isChecked
+ view.apply {
+ isEnabled = false
+ alpha = DISABLED_ALPHA
+ }
+ logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString())
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
}
}
@@ -219,5 +236,7 @@
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+ const val DISABLED_ALPHA = 0.3f
+ const val ENABLED_ALPHA = 1f
}
}
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 0000000..5d18dc1
--- /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 2865ad7..86e5dde 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 @@
@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 8e27493..f7e0de3 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.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 @@
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 @@
}
?: 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 @@
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 @@
.onEach {
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
- deviceItemInteractor.updateDeviceItems(context)
+ deviceItemInteractor.updateDeviceItems(
+ context,
+ DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+ )
}
}
.launchIn(this)
@@ -139,7 +153,9 @@
bluetoothStateInteractor.isBluetoothEnabled,
getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled),
this@BluetoothTileDialogViewModel,
+ systemClock,
uiEventLogger,
+ logger,
context
)
.apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
@@ -189,6 +205,7 @@
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 50eaf38..2c8d2a0 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 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 8c22614..7bb1619 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 @@
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 @@
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 @@
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 e196c6c..76fbf8e 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.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.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.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 @@
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 @@
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 @@
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 @@
private var deviceItemFactoryList: List<DeviceItemFactory> =
listOf(
+ ActiveMediaDeviceItemFactory(),
AvailableMediaDeviceItemFactory(),
ConnectedDeviceItemFactory(),
SavedDeviceItemFactory()
@@ -112,14 +120,16 @@
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 @@
?.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 @@
}
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 0000000..bb5a229
--- /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 0000000..761274e
--- /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 0000000..f7bec02
--- /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 0000000..6c1c1a3
--- /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/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
new file mode 100644
index 0000000..01df906
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.impl.di.QSTileComponent
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import dagger.Subcomponent
+
+@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 0000000..ccff8af
--- /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 0000000..558fb64
--- /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 0000000..e33b3e9
--- /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/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
index 0aa6b0b..4a4ba2b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
@@ -14,9 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.base.interactor
+package com.android.systemui.qs.tiles.impl.custom.di.bound
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+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/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
index 0aa6b0b..efc7431 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.base.interactor
+package com.android.systemui.qs.tiles.impl.custom.di.bound
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+import javax.inject.Qualifier
+
+/** 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 0000000..b3d916a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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
+
+/**
+ * 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 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 0000000..a412de3
--- /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/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 4a3bcae..c4d7dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -16,20 +16,49 @@
package com.android.systemui.qs.tiles.viewmodel
+import android.content.res.Resources
+import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.android.internal.logging.InstanceId
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
data class QSTileConfig(
val tileSpec: TileSpec,
- val tileIcon: Icon,
- @StringRes val tileLabelRes: Int,
+ val uiConfig: QSTileUIConfig,
val instanceId: InstanceId,
val metricsSpec: String = tileSpec.spec,
val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
)
+/**
+ * Static tile icon and label to be used when the fully operational tile isn't needed (ex. in edit
+ * mode). Icon and label are resources to better support config/locale changes.
+ */
+sealed interface QSTileUIConfig {
+
+ val tileIconRes: Int
+ @DrawableRes get
+ val tileLabelRes: Int
+ @StringRes get
+
+ /**
+ * Represents the absence of static UI state. This should be avoided by platform tiles in favour
+ * of [Resource]. Returns [Resources.ID_NULL] for each field.
+ */
+ data object Empty : QSTileUIConfig {
+ override val tileIconRes: Int
+ get() = Resources.ID_NULL
+ override val tileLabelRes: Int
+ get() = Resources.ID_NULL
+ }
+
+ /** Config containing actual icon and label resources. */
+ data class Resource(
+ @StringRes override val tileIconRes: Int,
+ @StringRes override val tileLabelRes: Int,
+ ) : QSTileUIConfig
+}
+
/** Represents policy restrictions that may be imposed on the tile. */
sealed interface QSTilePolicy {
/** Tile has no policy restrictions */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
new file mode 100644
index 0000000..3f3b94e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import com.android.internal.util.Preconditions
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QSTileConfigProvider {
+
+ /**
+ * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no
+ * config for such [tileSpec].
+ */
+ fun getConfig(tileSpec: String): QSTileConfig
+}
+
+@SysUISingleton
+class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) :
+ QSTileConfigProvider {
+
+ init {
+ for (entry in configs.entries) {
+ val configTileSpec = entry.value.tileSpec.spec
+ val keyTileSpec = entry.key
+ Preconditions.checkArgument(
+ configTileSpec == keyTileSpec,
+ "A wrong config is injected keySpec=$keyTileSpec configSpec=$configTileSpec"
+ )
+ }
+ }
+
+ override fun getConfig(tileSpec: String): QSTileConfig =
+ configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 0ccde74..dc5cccc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -59,7 +59,16 @@
// This represents a tile that is currently in a disabled state but is still interactable. A
// disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
// bluetooth disabled), but is still interactable by the user to modify this state.
- INACTIVE(Tile.STATE_INACTIVE),
+ INACTIVE(Tile.STATE_INACTIVE);
+
+ companion object {
+ fun valueOf(legacyState: Int): ActivationState =
+ when (legacyState) {
+ Tile.STATE_ACTIVE -> ACTIVE
+ Tile.STATE_INACTIVE -> INACTIVE
+ else -> UNAVAILABLE
+ }
+ }
}
/**
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 e5cb7ea..580c421 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 @@
*/
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 f6299e3..efa6da7 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,11 +17,13 @@
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
import com.android.internal.logging.InstanceId
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
@@ -31,9 +33,7 @@
import dagger.assisted.AssistedInject
import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -44,6 +44,7 @@
class QSTileViewModelAdapter
@AssistedInject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val qsHost: QSHost,
@Assisted private val qsTileViewModel: QSTileViewModel,
) : QSTile {
@@ -57,25 +58,28 @@
private val listeningClients: MutableCollection<Any> = mutableSetOf()
// Cancels the jobs when the adapter is no longer alive
- private val adapterScope = CoroutineScope(SupervisorJob())
+ private var availabilityJob: Job? = null
// Cancels the jobs when clients stop listening
- private val listeningScope = CoroutineScope(SupervisorJob())
+ private var stateJob: Job? = null
init {
- adapterScope.launch {
- qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
- if (!isAvailable) {
- qsHost.removeTile(tileSpec)
- }
- // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
- // why we only allow isAvailable == true once and throw an exception afterwards.
- if (index > 0 && isAvailable) {
- // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
- // guidance on how to auto add your tile
- throw UnsupportedOperationException("Turning on tile is not supported now")
+ availabilityJob =
+ applicationScope.launch {
+ qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+ if (!isAvailable) {
+ qsHost.removeTile(tileSpec)
+ }
+ // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
+ // That's
+ // why we only allow isAvailable == true once and throw an exception afterwards.
+ if (index > 0 && isAvailable) {
+ // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
+ // additional
+ // guidance on how to auto add your tile
+ throw UnsupportedOperationException("Turning on tile is not supported now")
+ }
}
}
- }
// QSTileHost doesn't call this when userId is initialized
userSwitch(qsHost.userId)
@@ -131,7 +135,7 @@
qsTileViewModel.currentState?.supportedActions?.contains(action) == true
override fun userSwitch(currentUser: Int) {
- qsTileViewModel.onUserIdChanged(currentUser)
+ qsTileViewModel.onUserChanged(UserHandle.of(currentUser))
}
@Deprecated(
@@ -140,25 +144,28 @@
)
override fun getMetricsCategory(): Int = 0
+ override fun isTileReady(): Boolean = qsTileViewModel.currentState != null
+
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
synchronized(listeningClients) {
if (listening) {
listeningClients.add(client)
if (listeningClients.size == 1) {
- qsTileViewModel.state
- .map { mapState(context, it, qsTileViewModel.config) }
- .onEach { legacyState ->
- synchronized(callbacks) {
- callbacks.forEach { it.onStateChanged(legacyState) }
+ stateJob =
+ qsTileViewModel.state
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ synchronized(callbacks) {
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
}
- }
- .launchIn(listeningScope)
+ .launchIn(applicationScope)
}
} else {
listeningClients.remove(client)
if (listeningClients.isEmpty()) {
- listeningScope.coroutineContext.cancelChildren()
+ stateJob?.cancel()
}
}
}
@@ -172,9 +179,9 @@
}
override fun destroy() {
- adapterScope.cancel()
- listeningScope.cancel()
- qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+ stateJob?.cancel()
+ availabilityJob?.cancel()
+ qsTileViewModel.destroy()
}
override fun getState(): QSTile.State? =
@@ -182,7 +189,13 @@
override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
override fun getTileLabel(): CharSequence =
- context.getString(qsTileViewModel.config.tileLabelRes)
+ with(qsTileViewModel.config.uiConfig) {
+ when (this) {
+ is QSTileUIConfig.Empty -> qsTileViewModel.currentState?.label ?: ""
+ is QSTileUIConfig.Resource -> context.getString(tileLabelRes)
+ }
+ }
+
override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec
private companion object {
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 f0650d3..9ba02b1 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 @@
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 @@
/** 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 @@
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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index f602da4..05f125f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -25,6 +25,7 @@
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.CountDownTimer;
+import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
@@ -141,6 +142,13 @@
return UserHandle.of(UserHandle.myUserId());
}
+ /**
+ * MediaProjection host is SystemUI for the screen recorder, so return 'my process uid'
+ */
+ private int getHostUid() {
+ return Process.myUid();
+ }
+
/** Create a dialog to show screen recording options to the user.
* If screen capturing is currently not allowed it will return a dialog
* that warns users about it. */
@@ -155,13 +163,22 @@
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
- mUserContextProvider.getUserContext().getUserId(),
- SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+ getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
- activityStarter, mUserContextProvider, onStartRecordingClicked)
- : new ScreenRecordDialog(context, this, mUserContextProvider,
+ ? new ScreenRecordPermissionDialog(
+ context,
+ getHostUserHandle(),
+ getHostUid(),
+ /* controller= */ this,
+ activityStarter,
+ mUserContextProvider,
+ onStartRecordingClicked,
+ mMediaProjectionMetricsLogger)
+ : new ScreenRecordDialog(
+ context,
+ /* controller= */ this,
+ mUserContextProvider,
onStartRecordingClicked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index f2e94e9..e7481cc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -66,8 +66,10 @@
private Switch mAudioSwitch;
private Spinner mOptions;
- public ScreenRecordDialog(Context context, RecordingController controller,
- UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
+ public ScreenRecordDialog(Context context,
+ RecordingController controller,
+ UserContextProvider userContextProvider,
+ @Nullable Runnable onStartRecordingClicked) {
super(context);
mController = controller;
mUserContextProvider = userContextProvider;
@@ -89,7 +91,6 @@
TextView cancelBtn = findViewById(R.id.button_cancel);
cancelBtn.setOnClickListener(v -> dismiss());
-
TextView startBtn = findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {
if (mOnStartRecordingClicked != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index ade93b1..f74234b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -33,6 +33,7 @@
import android.widget.Switch
import androidx.annotation.LayoutRes
import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -46,15 +47,19 @@
class ScreenRecordPermissionDialog(
context: Context,
private val hostUserHandle: UserHandle,
+ private val hostUid: Int,
private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
- private val onStartRecordingClicked: Runnable?
+ private val onStartRecordingClicked: Runnable?,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseScreenSharePermissionDialog(
context,
createOptionList(),
appName = null,
+ hostUid = hostUid,
+ mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
R.color.screenrecord_icon_color
) {
@@ -88,6 +93,7 @@
MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
hostUserHandle
)
+ intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
activityStarter.startActivity(intent, /* dismissShade= */ true)
}
dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index aa6bfc3..10d5f59 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -34,11 +34,11 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.util.TraceUtils.Companion.launch
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -63,7 +63,9 @@
user: UserHandle,
overrideTransition: Boolean,
) {
- applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ launchIntent(intent, options, user, overrideTransition)
+ }
}
suspend fun launchIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index c34fd42..f1c74c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,10 +20,10 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.TraceUtils.Companion.launch
+import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -88,7 +88,7 @@
* thread
*/
fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
- mainScope.launch {
+ mainScope.launch({ "$TAG#processAsync" }) {
val result = process(screenshot)
callback.accept(result)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 14e875d2..d2e4794 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import com.android.systemui.util.TraceUtils.Companion.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -47,7 +47,9 @@
}
override fun dismissKeyguard(callback: IOnDoneCallback) {
- lifecycleScope.launch { executeAfterDismissing(callback) }
+ lifecycleScope.launch("IScreenshotProxy#dismissKeyguard") {
+ executeAfterDismissing(callback)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index cd0cab5..1eae191 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -20,7 +20,7 @@
import android.util.Log
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.android.systemui.util.TraceUtils.Companion.async
import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -48,7 +48,7 @@
) : ScreenshotSoundController {
val player: Deferred<MediaPlayer?> =
- coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+ coroutineScope.async("loadCameraSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -58,12 +58,10 @@
}
override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
- player.await()?.start()
- }
+ return coroutineScope.async("playCameraSound", bgDispatcher) { player.await()?.start() }
}
override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+ return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 0158284..5684605 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -13,14 +13,11 @@
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.TraceUtils.Companion.launch
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.first
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -40,12 +37,7 @@
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
) {
- private lateinit var displays: StateFlow<Set<Display>>
- private val displaysCollectionJob: Job =
- mainScope.launch {
- displays = displayRepository.displays.stateIn(this, SharingStarted.Eagerly, emptySet())
- }
-
+ private val displays = displayRepository.displays
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
@@ -63,6 +55,7 @@
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
displayIds.forEach { displayId: Int ->
+ Log.d(TAG, "Executing screenshot for display $displayId")
dispatchToController(
rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
@@ -126,12 +119,12 @@
callback.reportError()
}
- private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+ private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> {
return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
// If this is a provided image, let's show the UI on the default display only.
listOf(Display.DEFAULT_DISPLAY)
} else {
- displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+ displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
}
}
@@ -163,7 +156,6 @@
screenshotController.onDestroy()
}
screenshotControllers.clear()
- displaysCollectionJob.cancel()
}
private fun getScreenshotController(id: Int): ScreenshotController {
@@ -184,7 +176,7 @@
onSaved: Consumer<Uri>,
requestCallback: RequestCallback
) {
- mainScope.launch {
+ mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8dc97c0..cc59f87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2629,7 +2629,6 @@
if (isPanelExpanded() != isExpanded) {
setExpandedOrAwaitingInputTransfer(isExpanded);
updateSystemUiStateFlags();
- mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
if (!isExpanded) {
mQsController.closeQsCustomizer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3d3447b..a2627ed 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.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 @@
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
CommunalViewModel communalViewModel,
CommunalRepository communalRepository,
- NotificationExpansionRepository notificationExpansionRepository,
+ NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
@@ -239,7 +239,7 @@
mLockscreenToDreamingTransition);
collectFlow(
mView,
- notificationExpansionRepository.isExpandAnimationRunning(),
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
this::setExpandAnimationRunning);
mClock = clock;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 3c68438..0ec7a36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -65,7 +65,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -77,6 +76,7 @@
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -1026,7 +1026,6 @@
&& mPanelViewControllerLazy.get().mAnimateBack) {
mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
}
- mShadeExpansionStateManager.onQsExpansionFractionChanged(qsExpansionFraction);
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 0554c58..9493989 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -36,10 +36,7 @@
class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
- private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
- private val qsExpansionFractionListeners =
- CopyOnWriteArrayList<ShadeQsExpansionFractionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@@ -67,15 +64,6 @@
expansionListeners.remove(listener)
}
- fun addFullExpansionListener(listener: ShadeFullExpansionListener) {
- fullExpansionListeners.add(listener)
- listener.onShadeExpansionFullyChanged(qsExpanded)
- }
-
- fun removeFullExpansionListener(listener: ShadeFullExpansionListener) {
- fullExpansionListeners.remove(listener)
- }
-
fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
qsExpansionListeners.add(listener)
listener.onQsExpansionChanged(qsExpanded)
@@ -85,25 +73,11 @@
qsExpansionListeners.remove(listener)
}
- fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
- qsExpansionFractionListeners.add(listener)
- listener.onQsExpansionFractionChanged(qsExpansionFraction)
- }
-
- fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
- qsExpansionFractionListeners.remove(listener)
- }
-
/** Adds a listener that will be notified when the panel state has changed. */
fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
}
- /** Removes a state listener. */
- fun removeStateListener(listener: ShadeStateListener) {
- stateListeners.remove(listener)
- }
-
override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
shadeStateEventsListeners.addIfAbsent(listener)
}
@@ -187,22 +161,6 @@
qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
}
- fun onQsExpansionFractionChanged(qsExpansionFraction: Float) {
- this.qsExpansionFraction = qsExpansionFraction
-
- debugLog("qsExpansionFraction=$qsExpansionFraction")
- qsExpansionFractionListeners.forEach {
- it.onQsExpansionFractionChanged(qsExpansionFraction)
- }
- }
-
- fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
- this.expanded = isExpanded
-
- debugLog("expanded=$isExpanded")
- fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) }
- }
-
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
deleted file mode 100644
index 6d13e19..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (c) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade
-
-/** A listener interface to be notified of expansion events for the notification shade. */
-fun interface ShadeFullExpansionListener {
- /** Invoked whenever the shade expansion changes, when it is fully collapsed or expanded */
- fun onShadeExpansionFullyChanged(isExpanded: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 2f68476..f043c71 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -148,12 +148,13 @@
*
* TODO(b/300258424) remove all but the first sentence of this comment
*/
- val isAnyExpanded: Flow<Boolean> =
+ val isAnyExpanded: StateFlow<Boolean> =
if (sceneContainerFlags.isEnabled()) {
- anyExpansion.map { it > 0f }.distinctUntilChanged()
- } else {
- repository.legacyExpandedOrAwaitingInputTransfer
- }
+ anyExpansion.map { it > 0f }.distinctUntilChanged()
+ } else {
+ repository.legacyExpandedOrAwaitingInputTransfer
+ }
+ .stateIn(scope, SharingStarted.Eagerly, false)
/**
* Whether the user is expanding or collapsing the shade with user input. This will be true even
@@ -184,7 +185,7 @@
* but a transition they initiated is still animating.
*/
val isUserInteracting: Flow<Boolean> =
- combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs }
+ combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
.distinctUntilChanged()
/** Are touches allowed on the notification panel? */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 77b0958..c8669072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
import android.view.View;
+import android.view.ViewPropertyAnimator;
import androidx.annotation.Nullable;
@@ -31,30 +33,42 @@
public static final long ANIMATION_DURATION_LENGTH = 210;
public static void fadeOut(final View view) {
- fadeOut(view, null);
+ fadeOut(view, (Runnable) null);
}
public static void fadeOut(final View view, final Runnable endRunnable) {
fadeOut(view, ANIMATION_DURATION_LENGTH, 0, endRunnable);
}
+ public static void fadeOut(final View view, final Animator.AnimatorListener listener) {
+ fadeOut(view, ANIMATION_DURATION_LENGTH, 0, listener);
+ }
+
+ public static void fadeOut(final View view, long duration, int delay) {
+ fadeOut(view, duration, delay, (Runnable) null);
+ }
+
+ /**
+ * Perform a fade-out animation, invoking {@code endRunnable} when the animation ends. It will
+ * not be invoked if the animation is cancelled.
+ *
+ * @deprecated Use {@link #fadeOut(View, long, int, Animator.AnimatorListener)} instead.
+ */
+ @Deprecated
public static void fadeOut(final View view, long duration, int delay,
- final Runnable endRunnable) {
+ @Nullable final Runnable endRunnable) {
view.animate().cancel();
view.animate()
.alpha(0f)
.setDuration(duration)
.setInterpolator(Interpolators.ALPHA_OUT)
.setStartDelay(delay)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- if (view.getVisibility() != View.GONE) {
- view.setVisibility(View.INVISIBLE);
- }
+ .withEndAction(() -> {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
}
});
if (view.hasOverlappingRendering()) {
@@ -62,6 +76,27 @@
}
}
+ public static void fadeOut(final View view, long duration, int delay,
+ @Nullable final Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .setStartDelay(delay)
+ .withEndAction(() -> {
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (listener != null) {
+ animator.setListener(listener);
+ }
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ }
+
public static void fadeOut(View view, float fadeOutAmount) {
fadeOut(view, fadeOutAmount, true /* remap */);
}
@@ -119,10 +154,21 @@
fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
}
- public static void fadeIn(final View view, long duration, int delay) {
- fadeIn(view, duration, delay, /* endRunnable= */ null);
+ public static void fadeIn(final View view, Animator.AnimatorListener listener) {
+ fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, listener);
}
+ public static void fadeIn(final View view, long duration, int delay) {
+ fadeIn(view, duration, delay, /* endRunnable= */ (Runnable) null);
+ }
+
+ /**
+ * Perform a fade-in animation, invoking {@code endRunnable} when the animation ends. It will
+ * not be invoked if the animation is cancelled.
+ *
+ * @deprecated Use {@link #fadeIn(View, long, int, Animator.AnimatorListener)} instead.
+ */
+ @Deprecated
public static void fadeIn(final View view, long duration, int delay,
@Nullable Runnable endRunnable) {
view.animate().cancel();
@@ -141,6 +187,26 @@
}
}
+ public static void fadeIn(final View view, long duration, int delay,
+ @Nullable Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setAlpha(0.0f);
+ view.setVisibility(View.VISIBLE);
+ }
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setStartDelay(delay)
+ .setInterpolator(Interpolators.ALPHA_IN);
+ if (listener != null) {
+ animator.setListener(listener);
+ }
+ if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+ view.animate().withLayer();
+ }
+ }
+
public static void fadeIn(View view, float fadeInAmount) {
fadeIn(view, fadeInAmount, false /* remap */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 33c42e0..ab015ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -72,10 +72,10 @@
return findViewById(R.id.no_notifications_footer);
}
- public void setTextColor(@ColorInt int color) {
- mEmptyText.setTextColor(color);
- mEmptyFooterText.setTextColor(color);
- mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(color));
+ public void setTextColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) {
+ mEmptyText.setTextColor(onSurfaceVariant);
+ mEmptyFooterText.setTextColor(onSurface);
+ mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
public void setText(@StringRes int text) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 2f1b589..dd24ca7 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 @@
}
private boolean canUnlockWithFingerprint() {
- return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+ return mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index ff4570e..49743bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,9 +31,11 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.util.time.SystemClock;
@@ -60,6 +62,7 @@
private final Context mContext;
private final NotificationManager mNotificationManager;
+ private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor;
private final SystemClock mSystemClock;
private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
@@ -76,12 +79,14 @@
public NotificationListener(
Context context,
NotificationManager notificationManager,
+ SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor,
SystemClock systemClock,
@Main Executor mainExecutor,
PluginManager pluginManager) {
super(pluginManager);
mContext = context;
mNotificationManager = notificationManager;
+ mStatusIconInteractor = statusIconInteractor;
mSystemClock = systemClock;
mMainExecutor = mainExecutor;
}
@@ -95,7 +100,9 @@
}
/** Registers a listener that's notified when any notification-related settings change. */
+ @Deprecated
public void addNotificationSettingsListener(NotificationSettingsListener listener) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
mSettingsListeners.add(listener);
}
@@ -230,8 +237,12 @@
@Override
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
- for (NotificationSettingsListener listener : mSettingsListeners) {
- listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
+ } else {
+ for (NotificationSettingsListener listener : mSettingsListeners) {
+ listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ }
}
}
@@ -294,6 +305,7 @@
return ranking;
}
+ @Deprecated
public interface NotificationSettingsListener {
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 61ebcc0..5f0b298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -48,11 +48,13 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
-import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -65,6 +67,7 @@
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -72,13 +75,16 @@
import java.util.Objects;
import java.util.function.Consumer;
+import javax.inject.Inject;
+
/**
* Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input
* interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
* and handling clicks on remote views.
*/
-public class NotificationRemoteInputManager implements Dumpable {
+@SysUISingleton
+public class NotificationRemoteInputManager implements CoreStartable {
public static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
public static boolean FORCE_REMOTE_INPUT_HISTORY =
@@ -94,6 +100,8 @@
private final NotificationVisibilityProvider mVisibilityProvider;
private final PowerInteractor mPowerInteractor;
private final ActionClickLogger mLogger;
+ private final JavaAdapter mJavaAdapter;
+ private final ShadeInteractor mShadeInteractor;
protected final Context mContext;
protected final NotifPipelineFlags mNotifPipelineFlags;
private final UserManager mUserManager;
@@ -249,6 +257,7 @@
/**
* Injected constructor. See {@link CentralSurfacesDependenciesModule}.
*/
+ @Inject
public NotificationRemoteInputManager(
Context context,
NotifPipelineFlags notifPipelineFlags,
@@ -261,7 +270,8 @@
RemoteInputControllerLogger remoteInputControllerLogger,
NotificationClickNotifier clickNotifier,
ActionClickLogger logger,
- DumpManager dumpManager) {
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor) {
mContext = context;
mNotifPipelineFlags = notifPipelineFlags;
mLockscreenUserManager = lockscreenUserManager;
@@ -269,6 +279,8 @@
mVisibilityProvider = visibilityProvider;
mPowerInteractor = powerInteractor;
mLogger = logger;
+ mJavaAdapter = javaAdapter;
+ mShadeInteractor = shadeInteractor;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -277,8 +289,25 @@
mRemoteInputUriController = remoteInputUriController;
mRemoteInputControllerLogger = remoteInputControllerLogger;
mClickNotifier = clickNotifier;
+ }
- dumpManager.registerDumpable(this);
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(mShadeInteractor.isAnyExpanded(),
+ this::onShadeOrQsExpanded);
+ }
+
+ private void onShadeOrQsExpanded(boolean expanded) {
+ if (expanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+ try {
+ mBarService.clearNotificationEffects();
+ } catch (RemoteException e) {
+ // Won't fail unless the world has ended.
+ }
+ }
+ if (!expanded) {
+ onPanelCollapsed();
+ }
}
/** Add a listener for various remote input events. Works with NEW pipeline only. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 37a4ef1..537f8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,15 +42,16 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
-import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,8 +65,7 @@
@SysUISingleton
public class StatusBarStateControllerImpl implements
SysuiStatusBarStateController,
- CallbackController<StateListener>,
- Dumpable {
+ CallbackController<StateListener> {
private static final String TAG = "SbStateController";
private static final boolean DEBUG_IMMERSIVE_APPS =
SystemProperties.getBoolean("persist.debug.immersive_apps", false);
@@ -95,6 +95,8 @@
private final ArrayList<RankedListener> mListeners = new ArrayList<>();
private final UiEventLogger mUiEventLogger;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private final JavaAdapter mJavaAdapter;
+ private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private int mState;
private int mLastState;
private int mUpcomingState;
@@ -156,18 +158,22 @@
@Inject
public StatusBarStateControllerImpl(
UiEventLogger uiEventLogger,
- DumpManager dumpManager,
InteractionJankMonitor interactionJankMonitor,
- ShadeExpansionStateManager shadeExpansionStateManager
- ) {
+ JavaAdapter javaAdapter,
+ Lazy<ShadeInteractor> shadeInteractorLazy) {
mUiEventLogger = uiEventLogger;
mInteractionJankMonitor = interactionJankMonitor;
+ mJavaAdapter = javaAdapter;
+ mShadeInteractorLazy = shadeInteractorLazy;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+ }
- dumpManager.registerDumpable(this);
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
+ this::onShadeOrQsExpanded);
}
@Override
@@ -345,7 +351,7 @@
}
}
- private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ private void onShadeOrQsExpanded(Boolean isExpanded) {
if (mIsExpanded != isExpanded) {
mIsExpanded = isExpanded;
String tag = getClass().getSimpleName() + "#setIsExpanded";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index aa32d5c..8104755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.view.View;
+import com.android.systemui.CoreStartable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -29,7 +30,7 @@
/**
* Sends updates to {@link StateListener}s about changes to the status bar state and dozing state
*/
-public interface SysuiStatusBarStateController extends StatusBarStateController {
+public interface SysuiStatusBarStateController extends StatusBarStateController, CoreStartable {
// TODO: b/115739177 (remove this explicit ordering if we can)
@Retention(SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 1fe6b83..a957095 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -33,24 +34,19 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
-import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -63,12 +59,13 @@
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
@@ -78,36 +75,12 @@
*/
@Module(includes = {StatusBarNotificationPresenterModule.class})
public interface CentralSurfacesDependenciesModule {
+
/** */
- @SysUISingleton
- @Provides
- static NotificationRemoteInputManager provideNotificationRemoteInputManager(
- Context context,
- NotifPipelineFlags notifPipelineFlags,
- NotificationLockscreenUserManager lockscreenUserManager,
- SmartReplyController smartReplyController,
- NotificationVisibilityProvider visibilityProvider,
- PowerInteractor powerInteractor,
- StatusBarStateController statusBarStateController,
- RemoteInputUriController remoteInputUriController,
- RemoteInputControllerLogger remoteInputControllerLogger,
- NotificationClickNotifier clickNotifier,
- ActionClickLogger actionClickLogger,
- DumpManager dumpManager) {
- return new NotificationRemoteInputManager(
- context,
- notifPipelineFlags,
- lockscreenUserManager,
- smartReplyController,
- visibilityProvider,
- powerInteractor,
- statusBarStateController,
- remoteInputUriController,
- remoteInputControllerLogger,
- clickNotifier,
- actionClickLogger,
- dumpManager);
- }
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationRemoteInputManager.class)
+ CoreStartable bindsStartNotificationRemoteInputManager(NotificationRemoteInputManager nrim);
/** */
@SysUISingleton
@@ -164,20 +137,23 @@
return new CommandQueue(context, displayTracker, registry, dumpHandler, powerInteractor);
}
- /**
- */
+ /** */
@Binds
ManagedProfileController provideManagedProfileController(
ManagedProfileControllerImpl controllerImpl);
- /**
- */
+ /** */
@Binds
SysuiStatusBarStateController providesSysuiStatusBarStateController(
StatusBarStateControllerImpl statusBarStateControllerImpl);
- /**
- */
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(SysuiStatusBarStateController.class)
+ CoreStartable bindsStartStatusBarStateController(StatusBarStateControllerImpl sbsc);
+
+ /** */
@Binds
StatusBarIconController provideStatusBarIconController(
StatusBarIconControllerImpl controllerImpl);
@@ -212,16 +188,14 @@
ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
- /**
- */
+ /** */
@Provides
@SysUISingleton
static ActivityLaunchAnimator provideActivityLaunchAnimator() {
return new ActivityLaunchAnimator();
}
- /**
- */
+ /** */
@Provides
@SysUISingleton
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
@@ -253,8 +227,7 @@
return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags);
}
- /**
- */
+ /** */
@Provides
@SysUISingleton
static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index e2de37f..22912df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,18 +17,13 @@
package com.android.systemui.statusbar.dagger
import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
/**
* A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -38,24 +33,9 @@
* ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
-@Module
+@Module(includes = [StatusBarDataLayerModule::class])
abstract class StatusBarModule {
@Binds
- abstract fun bindStatusBarModeRepository(
- impl: StatusBarModeRepositoryImpl
- ): StatusBarModeRepository
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryImpl::class)
- abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
-
- @Binds
- abstract fun bindKeyguardStatusBarRepository(
- impl: KeyguardStatusBarRepositoryImpl
- ): KeyguardStatusBarRepository
-
- @Binds
@IntoMap
@ClassKey(OngoingCallController::class)
abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@@ -64,10 +44,4 @@
@IntoMap
@ClassKey(LightBarController::class)
abstract fun bindLightBarController(impl: LightBarController): CoreStartable
-
- @Binds
- @IntoSet
- abstract fun statusBarInitializedListener(
- statusBarModeRepository: StatusBarModeRepository,
- ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
new file mode 100644
index 0000000..29d53fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.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.statusbar.data
+
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ KeyguardStatusBarRepositoryModule::class,
+ StatusBarModeRepositoryModule::class,
+ StatusBarPhoneDataLayerModule::class
+ ]
+)
+object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index 8136de9..d1594ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.user.data.repository.UserSwitcherRepository
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -78,3 +80,8 @@
isEnabled && isKeyguardEnabled
}
}
+
+@Module
+interface KeyguardStatusBarRepositoryModule {
+ @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
new file mode 100644
index 0000000..2c706a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Exposes state pertaining to settings tracked over the [NotificationListener] boundary. */
+@SysUISingleton
+class NotificationListenerSettingsRepository @Inject constructor() {
+ /** Should icons for silent notifications be shown in the status bar? */
+ val showSilentStatusIcons = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 2b05994..47994d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -30,7 +30,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
@@ -38,6 +38,11 @@
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -56,7 +61,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
+interface StatusBarModeRepository {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -112,7 +117,7 @@
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable {
+) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -334,3 +339,17 @@
val statusBarBounds: BoundsPair,
)
}
+
+@Module
+interface StatusBarModeRepositoryModule {
+ @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryImpl::class)
+ fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+ @Binds
+ @IntoSet
+ fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
new file mode 100644
index 0000000..1248b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import javax.inject.Inject
+
+class SilentNotificationStatusIconsVisibilityInteractor
+@Inject
+constructor(private val repository: NotificationListenerSettingsRepository) {
+ /** Set whether icons for silent notifications be hidden in the status bar. */
+ fun setHideSilentStatusIcons(hideIcons: Boolean) {
+ repository.showSilentStatusIcons.value = !hideIcons
+ }
+}
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 756151b..96279e2 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 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 @@
/** 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 @@
onFinishAnimationCallback: Runnable? = null
): NotificationLaunchAnimatorController {
return NotificationLaunchAnimatorController(
- notificationExpansionRepository,
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
notification,
@@ -60,7 +60,7 @@
* 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 @@
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 @@
// 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 @@
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/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index e763797..7b3a93a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -225,7 +225,7 @@
/** @see NotifPipeline#getEntry(String) () */
@Nullable
- NotificationEntry getEntry(@NonNull String key) {
+ public NotificationEntry getEntry(@NonNull String key) {
return mNotificationSet.get(key);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2d83970..0c69a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -37,8 +37,8 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -85,7 +85,7 @@
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
- private val notificationListInteractor: NotificationListInteractor,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
) : Coordinator, Dumpable {
@@ -351,7 +351,7 @@
override fun onCleanup() {
logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
- notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+ seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
hasFilteredAnyNotifs = false
}
}
@@ -389,7 +389,7 @@
with(pw.asIndenting()) {
println(
"notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
- notificationListInteractor.hasFilteredOutSeenNotifications.value
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
)
println("unseen notifications:")
indentIfPossible {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 62a0d13..e0c4bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -22,19 +22,24 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
- * A small coordinator which updates the notif stack (the view layer which holds notifications)
- * with high-level data after the stack is populated with the final entries.
+ * A small coordinator which updates the notif stack (the view layer which holds notifications) with
+ * high-level data after the stack is populated with the final entries.
*/
@CoordinatorScope
-class StackCoordinator @Inject internal constructor(
+class StackCoordinator
+@Inject
+internal constructor(
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
- private val notificationIconAreaController: NotificationIconAreaController
+ private val notificationIconAreaController: NotificationIconAreaController,
+ private val renderListInteractor: RenderNotificationListInteractor,
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
@@ -45,7 +50,11 @@
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
controller.setNotifStats(calculateNotifStats(entries))
- notificationIconAreaController.updateNotificationIcons(entries)
+ if (NotificationIconContainerRefactor.isEnabled) {
+ renderListInteractor.setRenderedList(entries)
+ } else {
+ notificationIconAreaController.updateNotificationIcons(entries)
+ }
}
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -75,4 +84,4 @@
hasClearableSilentNotifs = hasClearableSilentNotifs
)
}
-}
\ No newline at end of file
+}
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 8561869..fa366c6 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.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 @@
@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/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
new file mode 100644
index 0000000..8064f04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Repository of "active" notifications in the notification list.
+ *
+ * This repository serves as the boundary between the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] and the modern
+ * notifications presentation codebase.
+ */
+@SysUISingleton
+class ActiveNotificationListRepository @Inject constructor() {
+ /**
+ * Notifications actively presented to the user in the notification stack.
+ *
+ * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+ */
+ val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+
+ /** Are any already-seen notifications currently filtered out of the active list? */
+ val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
new file mode 100644
index 0000000..afed6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** View-states pertaining to heads-up notification icons. */
+@SysUISingleton
+class HeadsUpNotificationIconViewStateRepository @Inject constructor() {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedNotification = MutableStateFlow<String?>(null)
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedIconLocation = MutableStateFlow<Rect?>(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
deleted file mode 100644
index 6f0a97a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.data.repository
-
-import android.util.Log
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-private const val TAG = "NotificationExpansionRepository"
-
-/** A repository tracking the status of notification expansion animations. */
-@SysUISingleton
-class NotificationExpansionRepository @Inject constructor() {
- private val _isExpandAnimationRunning = MutableStateFlow(false)
-
- /**
- * Emits true if an animation that expands a notification object into an opening window is
- * running and false otherwise.
- *
- * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
- */
- val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow()
-
- /** Sets whether the notification expansion animation is currently running. */
- fun setIsExpandAnimationRunning(running: Boolean) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
- Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
- }
- _isExpandAnimationRunning.value = running
- }
-}
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 0000000..9b56299
--- /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/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
new file mode 100644
index 0000000..bfec60bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class ActiveNotificationsInteractor
+@Inject
+constructor(
+ repository: ActiveNotificationListRepository,
+) {
+ /** Notifications actively presented to the user in the notification stack, in order. */
+ val notifications: Flow<Collection<ActiveNotificationModel>> =
+ repository.activeNotifications.map { it.values }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
new file mode 100644
index 0000000..17b6e9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to heads up notification icons. */
+class HeadsUpNotificationIconInteractor
+@Inject
+constructor(
+ private val repository: HeadsUpNotificationIconViewStateRepository,
+) {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation
+
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedNotification: Flow<String?> = repository.isolatedNotification
+
+ /** Updates the location where isolated notification icons are shown. */
+ fun setIsolatedIconLocation(rect: Rect?) {
+ repository.isolatedIconLocation.value = rect
+ }
+
+ /** Updates which notification will have its icon displayed isolated. */
+ fun setIsolatedIconNotificationKey(key: String?) {
+ repository.isolatedNotification.value = key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8f7e269..8079ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
@@ -20,15 +20,14 @@
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import javax.inject.Inject
-/** Interactor for notifications in general. */
+/** Interactor for notification alerting. */
@SysUISingleton
-class NotificationsInteractor
+class NotificationAlertsInteractor
@Inject
constructor(
private val disableFlagsRepository: DisableFlagsRepository,
) {
/** Returns true if notification alerts are allowed. */
- fun areNotificationAlertsEnabled(): Boolean {
- return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
- }
+ fun areNotificationAlertsEnabled(): Boolean =
+ disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
new file mode 100644
index 0000000..22ce4f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.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.StateFlow
+
+/** A repository tracking the status of notification expansion animations. */
+@SysUISingleton
+class NotificationLaunchAnimationInteractor
+@Inject
+constructor(private val repository: NotificationLaunchAnimationRepository) {
+
+ /**
+ * Emits true if an animation that expands a notification object into an opening window is
+ * running and false otherwise.
+ *
+ * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
+ */
+ val isLaunchAnimationRunning: StateFlow<Boolean>
+ get() = repository.isLaunchAnimationRunning
+
+ /** Sets whether the notification expansion launch animation is currently running. */
+ fun setIsLaunchAnimationRunning(running: Boolean) {
+ if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
+ }
+ repository.isLaunchAnimationRunning.value = running
+ }
+
+ companion object {
+ private const val TAG = "NotificationLaunchAnimationInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
new file mode 100644
index 0000000..604ecbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.domain.interactor
+
+import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.update
+
+private typealias ModelStore = Map<String, ActiveNotificationModel>
+
+/**
+ * Logic for passing information from the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
+ * layers.
+ */
+class RenderNotificationListInteractor
+@Inject
+constructor(
+ private val repository: ActiveNotificationListRepository,
+ private val sectionStyleProvider: SectionStyleProvider,
+) {
+ /**
+ * Sets the current list of rendered notification entries as displayed in the notification
+ * stack.
+ *
+ * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+ */
+ fun setRenderedList(entries: List<ListEntry>) {
+ repository.activeNotifications.update { existingModels ->
+ entries.associateBy(
+ keySelector = { it.key },
+ valueTransform = { it.toModel(existingModels) },
+ )
+ }
+ }
+
+ private fun ListEntry.toModel(
+ existingModels: ModelStore,
+ ): ActiveNotificationModel =
+ existingModels.createOrReuse(
+ key = key,
+ groupKey = representativeEntry?.sbn?.groupKey,
+ isAmbient = sectionStyleProvider.isMinimized(this),
+ isRowDismissed = representativeEntry?.isRowDismissed == true,
+ isSilent = sectionStyleProvider.isSilent(this),
+ isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
+ isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
+ isPulsing = representativeEntry?.showingPulsing() == true,
+ aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
+ shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
+ statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+ )
+
+ private fun ModelStore.createOrReuse(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): ActiveNotificationModel {
+ return this[key]?.takeIf {
+ it.isCurrent(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon
+ )
+ }
+ ?: ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
+ }
+
+ private fun ActiveNotificationModel.isCurrent(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): Boolean {
+ return when {
+ key != this.key -> false
+ groupKey != this.groupKey -> false
+ isAmbient != this.isAmbient -> false
+ isRowDismissed != this.isRowDismissed -> false
+ isSilent != this.isSilent -> false
+ isLastMessageFromReply != this.isLastMessageFromReply -> false
+ isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+ isPulsing != this.isPulsing -> false
+ aodIcon != this.aodIcon -> false
+ shelfIcon != this.shelfIcon -> false
+ statusBarIcon != this.statusBarIcon -> false
+ else -> true
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 3fd68a8..1f7ab96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -14,25 +14,26 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.domain.interactor
+package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
-class NotificationListInteractor
+class SeenNotificationsInteractor
@Inject
constructor(
- private val notificationListRepository: NotificationListRepository,
+ private val notificationListRepository: ActiveNotificationListRepository,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
- val hasFilteredOutSeenNotifications: StateFlow<Boolean>
- get() = notificationListRepository.hasFilteredOutSeenNotifications
+ val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+ notificationListRepository.hasFilteredOutSeenNotifications
+ /** Set whether already-seen notifications are currently filtered out of the shade. */
fun setHasFilteredOutSeenNotifications(value: Boolean) {
- notificationListRepository.setHasFilteredOutSeenNotifications(value)
+ notificationListRepository.hasFilteredOutSeenNotifications.value = value
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index e74b3fc..9c51e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -19,6 +19,9 @@
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -35,6 +38,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -44,6 +48,8 @@
import java.io.PrintWriter;
public class FooterView extends StackScrollerDecorView {
+ private static final String TAG = "FooterView";
+
private FooterViewButton mClearAllButton;
private FooterViewButton mManageButton;
private boolean mShowHistory;
@@ -57,6 +63,9 @@
private String mSeenNotifsFilteredText;
private Drawable mSeenNotifsFilteredIcon;
+ private @StringRes int mMessageStringId;
+ private @DrawableRes int mMessageIconId;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -84,6 +93,50 @@
});
}
+ /** Set the string for a message to be shown instead of the buttons. */
+ public void setMessageString(@StringRes int messageId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageStringId == messageId) {
+ return; // nothing changed
+ }
+ mMessageStringId = messageId;
+ updateMessageString();
+ }
+
+ private void updateMessageString() {
+ if (mMessageStringId == 0) {
+ return; // not initialized yet
+ }
+ String messageString = getContext().getString(mMessageStringId);
+ mSeenNotifsFooterTextView.setText(messageString);
+ }
+
+
+ /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
+ public void setMessageIcon(@DrawableRes int iconId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageIconId == iconId) {
+ return; // nothing changed
+ }
+ mMessageIconId = iconId;
+ updateMessageIcon();
+ }
+
+ private void updateMessageIcon() {
+ if (mMessageIconId == 0) {
+ return; // not initialized yet
+ }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ @SuppressLint("UseCompatLoadingForDrawables")
+ Drawable messageIcon = getContext().getDrawable(mMessageIconId);
+ if (messageIcon != null) {
+ messageIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(messageIcon, null, null, null);
+ }
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -148,9 +201,11 @@
mManageButton.setText(mManageNotificationText);
mManageButton.setContentDescription(mManageNotificationText);
}
- mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
- mSeenNotifsFooterTextView
- .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ if (!FooterViewRefactor.isEnabled()) {
+ mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ }
}
/** Whether the start button shows "History" (true) or "Manage" (false). */
@@ -167,6 +222,11 @@
mContext.getString(R.string.accessibility_clear_all));
updateResources();
updateContent();
+
+ if (FooterViewRefactor.isEnabled()) {
+ updateMessageString();
+ updateMessageIcon();
+ }
}
/**
@@ -174,37 +234,37 @@
*/
public void updateColors() {
Resources.Theme theme = mContext.getTheme();
- final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme);
+ final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
+ com.android.internal.R.attr.materialColorOnSurface);
+ final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext,
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh);
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
// TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
- final @ColorInt int buttonBgColor =
- Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface);
- final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP);
- if (buttonBgColor != 0) {
+ final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
+ if (scHigh != 0) {
clearAllBg.setColorFilter(bgColorFilter);
manageBg.setColorFilter(bgColorFilter);
}
mClearAllButton.setBackground(clearAllBg);
- mClearAllButton.setTextColor(textColor);
+ mClearAllButton.setTextColor(onSurface);
mManageButton.setBackground(manageBg);
- mManageButton.setTextColor(textColor);
- final @ColorInt int labelTextColor =
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
- mSeenNotifsFooterTextView.setTextColor(labelTextColor);
- mSeenNotifsFooterTextView.setCompoundDrawableTintList(
- ColorStateList.valueOf(labelTextColor));
+ mManageButton.setTextColor(onSurface);
+ mSeenNotifsFooterTextView.setTextColor(onSurface);
+ mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
private void updateResources() {
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ if (!FooterViewRefactor.isEnabled()) {
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
new file mode 100644
index 0000000..6d823437
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.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.statusbar.notification.footer.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [FooterView] to its [view model][FooterViewModel]. */
+object FooterViewBinder {
+ fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ): DisposableHandle {
+ return footer.repeatWhenAttached {
+ // Listen for changes when the view is attached.
+ lifecycleScope.launch {
+ viewModel.message.collect { message ->
+ footer.setFooterLabelVisible(message.visible)
+ footer.setMessageString(message.messageId)
+ footer.setMessageIcon(message.iconId)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
index 0aa6b0b..bc912fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.base.interactor
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+
+/** A ViewModel for the string message that can be shown in the footer. */
+data class FooterMessageViewModel(
+ @StringRes val messageId: Int,
+ @DrawableRes val iconId: Int,
+ val visible: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
new file mode 100644
index 0000000..ffa5ff0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [FooterView]. */
+@SysUISingleton
+class FooterViewModel
+@Inject
+constructor(seenNotificationsInteractor: SeenNotificationsInteractor) {
+ init {
+ /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
+ }
+
+ val message: Flow<FooterMessageViewModel> =
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ visible = hasFilteredOutNotifs,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
new file mode 100644
index 0000000..00d873e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+
+/** Domain logic related to notification icons. */
+class NotificationIconsInteractor
+@Inject
+constructor(
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ private val bubbles: Optional<Bubbles>,
+ private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
+) {
+ /** Returns a subset of all active notifications based on the supplied filtration parameters. */
+ fun filteredNotifSet(
+ showAmbient: Boolean = true,
+ showLowPriority: Boolean = true,
+ showDismissed: Boolean = true,
+ showRepliedMessages: Boolean = true,
+ showPulsing: Boolean = true,
+ ): Flow<Set<ActiveNotificationModel>> {
+ return combine(
+ activeNotificationsInteractor.notifications,
+ keyguardViewStateRepository.areNotificationsFullyHidden,
+ ) { notifications, notifsFullyHidden ->
+ notifications
+ .asSequence()
+ .filter { model: ActiveNotificationModel ->
+ shouldShowNotificationIcon(
+ model = model,
+ showAmbient = showAmbient,
+ showLowPriority = showLowPriority,
+ showDismissed = showDismissed,
+ showRepliedMessages = showRepliedMessages,
+ showPulsing = showPulsing,
+ notifsFullyHidden = notifsFullyHidden,
+ )
+ }
+ .toSet()
+ }
+ }
+
+ private fun shouldShowNotificationIcon(
+ model: ActiveNotificationModel,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ showDismissed: Boolean,
+ showRepliedMessages: Boolean,
+ showPulsing: Boolean,
+ notifsFullyHidden: Boolean,
+ ): Boolean {
+ return when {
+ !showAmbient && model.isAmbient -> false
+ !showLowPriority && model.isSilent -> false
+ !showDismissed && model.isRowDismissed -> false
+ !showRepliedMessages && model.isLastMessageFromReply -> false
+ !showAmbient && model.isSuppressedFromStatusBar -> false
+ !showPulsing && model.isPulsing && !notifsFullyHidden -> false
+ bubbles.getOrNull()?.isBubbleExpanded(model.key) == true -> false
+ else -> true
+ }
+ }
+}
+
+/** Domain logic related to notification icons shown on the always-on display. */
+class AlwaysOnDisplayNotificationIconsInteractor
+@Inject
+constructor(
+ deviceEntryInteractor: DeviceEntryInteractor,
+ iconsInteractor: NotificationIconsInteractor,
+) {
+ val aodNotifs: Flow<Set<ActiveNotificationModel>> =
+ deviceEntryInteractor.isBypassEnabled.flatMapLatest { isBypassEnabled ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showDismissed = false,
+ showRepliedMessages = false,
+ showPulsing = !isBypassEnabled,
+ )
+ }
+}
+
+/** Domain logic related to notification icons shown in the status bar. */
+class StatusBarNotificationIconsInteractor
+@Inject
+constructor(
+ iconsInteractor: NotificationIconsInteractor,
+ settingsRepository: NotificationListenerSettingsRepository,
+) {
+ val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
+ settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showLowPriority = showSilentIcons,
+ showDismissed = false,
+ showRepliedMessages = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index eb5c1fa..1992ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -16,52 +16,17 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.content.Context
-import android.graphics.Color
import android.graphics.Rect
-import android.os.Bundle
-import android.os.Trace
-import android.view.LayoutInflater
import android.view.View
-import android.widget.FrameLayout
-import androidx.annotation.ColorInt
-import androidx.annotation.VisibleForTesting
-import androidx.collection.ArrayMap
-import com.android.internal.statusbar.StatusBarIcon
-import com.android.internal.util.ContrastColorUtil
-import com.android.settingslib.Utils
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.demomode.DemoMode
-import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
-import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationListener
-import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.wm.shell.bubbles.Bubbles
-import java.util.Optional
-import java.util.function.Function
import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
/**
* Controller class for [NotificationIconContainer]. This implementation serves as a temporary
@@ -71,514 +36,44 @@
* can be used directly.
*/
@SysUISingleton
-class NotificationIconAreaControllerViewBinderWrapperImpl
-@Inject
-constructor(
- private val context: Context,
- private val wakeUpCoordinator: NotificationWakeUpCoordinator,
- private val bypassController: KeyguardBypassController,
- private val configurationController: ConfigurationController,
- private val mediaManager: NotificationMediaManager,
- notificationListener: NotificationListener,
- private val dozeParameters: DozeParameters,
- private val sectionStyleProvider: SectionStyleProvider,
- private val bubblesOptional: Optional<Bubbles>,
- demoModeController: DemoModeController,
- darkIconDispatcher: DarkIconDispatcher,
- private val featureFlags: FeatureFlagsClassic,
- private val statusBarWindowController: StatusBarWindowController,
- private val screenOffAnimationController: ScreenOffAnimationController,
- private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
- private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
- private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) :
- NotificationIconAreaController,
- DarkIconDispatcher.DarkReceiver,
- NotificationWakeUpCoordinator.WakeUpListener,
- DemoMode {
-
- private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
- private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
- private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
- private val tintAreas = ArrayList<Rect>()
-
- private var iconSize = 0
- private var iconHPadding = 0
- private var iconTint = Color.WHITE
- private var notificationEntries = listOf<ListEntry>()
- private var notificationIconArea: View? = null
- private var notificationIcons: NotificationIconContainer? = null
- private var shelfIcons: NotificationIconContainer? = null
- private var aodIcons: NotificationIconContainer? = null
- private var aodBindJob: DisposableHandle? = null
- private var aodIconAppearTranslation = 0
- private var aodIconTint = 0
- private var showLowPriority = true
-
- @VisibleForTesting
- val settingsListener: NotificationListener.NotificationSettingsListener =
- object : NotificationListener.NotificationSettingsListener {
- override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
- showLowPriority = !hideSilentStatusIcons
- updateStatusBarIcons()
- }
- }
-
- init {
- wakeUpCoordinator.addListener(this)
- demoModeController.addCallback(this)
- notificationListener.addNotificationSettingsListener(settingsListener)
- initializeNotificationAreaViews(context)
- reloadAodColor()
- darkIconDispatcher.addDarkReceiver(this)
- }
-
- @VisibleForTesting
- fun shouldShowLowPriorityIcons(): Boolean {
- return showLowPriority
- }
+class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor() :
+ NotificationIconAreaController {
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
- override fun setupAodIcons(aodIcons: NotificationIconContainer) {
- val changed = this.aodIcons != null && aodIcons !== this.aodIcons
- if (changed) {
- this.aodIcons!!.setAnimationsEnabled(false)
- this.aodIcons!!.removeAllViews()
- aodBindJob?.dispose()
- }
- this.aodIcons = aodIcons
- this.aodIcons!!.setOnLockScreen(true)
- aodBindJob =
- NotificationIconContainerViewBinder.bind(
- aodIcons,
- aodIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- if (changed) {
- updateAodNotificationIcons()
- }
- updateIconLayoutParams(context)
- }
+ override fun setupAodIcons(aodIcons: NotificationIconContainer?) = unsupported
override fun setupShelf(notificationShelfController: NotificationShelfController) =
NotificationShelfViewBinderWrapperControllerImpl.unsupported
- override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
- }
+ override fun setShelfIcons(icons: NotificationIconContainer) = unsupported
- override fun onDensityOrFontScaleChanged(context: Context) {
- updateIconLayoutParams(context)
- }
+ override fun onDensityOrFontScaleChanged(context: Context) = unsupported
/** Returns the view that represents the notification area. */
- override fun getNotificationInnerAreaView(): View? {
- return notificationIconArea
- }
-
- /**
- * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
- * color that should be used to tint any icons in the notification area.
- *
- * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
- * @param darkIntensity
- */
- override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
- this.tintAreas.clear()
- this.tintAreas.addAll(tintAreas)
- if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
- this.iconTint = iconTint
- }
- applyNotificationIconsTint()
- }
+ override fun getNotificationInnerAreaView(): View? = unsupported
/** Updates the notifications with the given list of notifications to display. */
- override fun updateNotificationIcons(entries: List<ListEntry>) {
- notificationEntries = entries
- updateNotificationIcons()
- }
+ override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
- private fun updateStatusBarIcons() {
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.statusBarIcon },
- notificationIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = showLowPriority,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = false /* hideCurrentMedia */,
- hidePulsing = false /* hidePulsing */
- )
- }
+ override fun updateAodNotificationIcons() = unsupported
- override fun updateAodNotificationIcons() {
- if (aodIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.aodIcon },
- aodIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = true /* showLowPriority */,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = true /* hideCurrentMedia */,
- hidePulsing = bypassController.bypassEnabled /* hidePulsing */
- )
- }
+ override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
- override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
- notificationIcons!!.showIconIsolated(icon, animated)
- }
-
- override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
- notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
- }
+ override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
+ unsupported
override fun setAnimationsEnabled(enabled: Boolean) = unsupported
- override fun onThemeChanged() {
- reloadAodColor()
- updateAodIconColors()
- }
+ override fun onThemeChanged() = unsupported
- override fun getHeight(): Int {
- return if (aodIcons == null) 0 else aodIcons!!.height
- }
-
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- updateAodNotificationIcons()
- updateAodIconColors()
- }
-
- override fun demoCommands(): List<String> {
- val commands = ArrayList<String>()
- commands.add(DemoMode.COMMAND_NOTIFICATIONS)
- return commands
- }
-
- override fun dispatchDemoCommand(command: String, args: Bundle) {
- if (notificationIconArea != null) {
- val visible = args.getString("visible")
- val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
- notificationIconArea?.visibility = vis
- }
- }
-
- override fun onDemoModeFinished() {
- if (notificationIconArea != null) {
- notificationIconArea?.visibility = View.VISIBLE
- }
- }
-
- private fun inflateIconArea(inflater: LayoutInflater): View {
- return inflater.inflate(R.layout.notification_icon_area, null)
- }
-
- /** Initializes the views that will represent the notification area. */
- private fun initializeNotificationAreaViews(context: Context) {
- reloadDimens(context)
- val layoutInflater = LayoutInflater.from(context)
- notificationIconArea = inflateIconArea(layoutInflater)
- notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
- NotificationIconContainerViewBinder.bind(
- notificationIcons!!,
- statusBarIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- }
-
- private fun updateIconLayoutParams(context: Context) {
- reloadDimens(context)
- val params = generateIconLayoutParams()
- for (i in 0 until notificationIcons!!.childCount) {
- val child = notificationIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- if (shelfIcons != null) {
- for (i in 0 until shelfIcons!!.childCount) {
- val child = shelfIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val child = aodIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- }
-
- private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
- return FrameLayout.LayoutParams(
- iconSize + 2 * iconHPadding,
- statusBarWindowController.statusBarHeight
- )
- }
-
- private fun reloadDimens(context: Context) {
- val res = context.resources
- iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
- iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
- aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
- }
-
- private fun shouldShowNotificationIcon(
- entry: NotificationEntry,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean
- ): Boolean {
- if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
- return false
- }
- if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
- return false
- }
- if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
- return false
- }
- if (entry.isRowDismissed && hideDismissed) {
- return false
- }
- if (hideRepliedMessages && entry.isLastMessageFromReply) {
- return false
- }
- // showAmbient == show in shade but not shelf
- if (!showAmbient && entry.shouldSuppressStatusBar()) {
- return false
- }
- if (
- hidePulsing &&
- entry.showingPulsing() &&
- (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
- ) {
- return false
- }
- return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
- false
- } else true
- }
-
- private fun updateNotificationIcons() {
- Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
- updateStatusBarIcons()
- updateShelfIcons()
- updateAodNotificationIcons()
- applyNotificationIconsTint()
- Trace.endSection()
- }
-
- private fun updateShelfIcons() {
- if (shelfIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.shelfIcon },
- shelfIcons,
- showAmbient = true,
- showLowPriority = true,
- hideDismissed = false,
- hideRepliedMessages = false,
- hideCurrentMedia = false,
- hidePulsing = false
- )
- }
-
- /**
- * Updates the notification icons for a host layout. This will ensure that the notification host
- * layout will have the same icons like the ones in here.
- *
- * @param function A function to look up an icon view based on an entry
- * @param hostLayout which layout should be updated
- * @param showAmbient should ambient notification icons be shown
- * @param showLowPriority should icons from silent notifications be shown
- * @param hideDismissed should dismissed icons be hidden
- * @param hideRepliedMessages should messages that have been replied to be hidden
- * @param hidePulsing should pulsing notifications be hidden
- */
- private fun updateIconsForLayout(
- function: Function<NotificationEntry, StatusBarIconView?>,
- hostLayout: NotificationIconContainer?,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean,
- ) {
- val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
- // Filter out ambient notifications and notification children.
- for (i in notificationEntries.indices) {
- val entry = notificationEntries[i].representativeEntry
- if (entry != null && entry.row != null) {
- if (
- shouldShowNotificationIcon(
- entry,
- showAmbient,
- showLowPriority,
- hideDismissed,
- hideRepliedMessages,
- hideCurrentMedia,
- hidePulsing
- )
- ) {
- val iconView = function.apply(entry)
- if (iconView != null) {
- toShow.add(iconView)
- }
- }
- }
- }
-
- // In case we are changing the suppression of a group, the replacement shouldn't flicker
- // and it should just be replaced instead. We therefore look for notifications that were
- // just replaced by the child or vice-versa to suppress this.
- val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
- val toRemove = ArrayList<View>()
- for (i in 0 until hostLayout!!.childCount) {
- val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
- if (!toShow.contains(child)) {
- var iconWasReplaced = false
- val removedGroupKey = child.notification.groupKey
- for (j in toShow.indices) {
- val candidate = toShow[j]
- if (
- candidate.sourceIcon.sameAs(child.sourceIcon) &&
- candidate.notification.groupKey == removedGroupKey
- ) {
- if (!iconWasReplaced) {
- iconWasReplaced = true
- } else {
- iconWasReplaced = false
- break
- }
- }
- }
- if (iconWasReplaced) {
- var statusBarIcons = replacingIcons[removedGroupKey]
- if (statusBarIcons == null) {
- statusBarIcons = ArrayList()
- replacingIcons[removedGroupKey] = statusBarIcons
- }
- statusBarIcons.add(child.statusBarIcon)
- }
- toRemove.add(child)
- }
- }
- // removing all duplicates
- val duplicates = ArrayList<String?>()
- for (key in replacingIcons.keys) {
- val statusBarIcons = replacingIcons[key]!!
- if (statusBarIcons.size != 1) {
- duplicates.add(key)
- }
- }
- replacingIcons.removeAll(duplicates)
- hostLayout.setReplacingIcons(replacingIcons)
- val toRemoveCount = toRemove.size
- for (i in 0 until toRemoveCount) {
- hostLayout.removeView(toRemove[i])
- }
- val params = generateIconLayoutParams()
- for (i in toShow.indices) {
- val v = toShow[i]
- // The view might still be transiently added if it was just removed and added again
- hostLayout.removeTransientView(v)
- if (v.parent == null) {
- if (hideDismissed) {
- v.setOnDismissListener(updateStatusBarIcons)
- }
- hostLayout.addView(v, i, params)
- }
- }
- hostLayout.setChangingViewPositions(true)
- // Re-sort notification icons
- val childCount = hostLayout.childCount
- for (i in 0 until childCount) {
- val actual = hostLayout.getChildAt(i)
- val expected = toShow[i]
- if (actual === expected) {
- continue
- }
- hostLayout.removeView(expected)
- hostLayout.addView(expected, i)
- }
- hostLayout.setChangingViewPositions(false)
- hostLayout.setReplacingIcons(null)
- }
-
- /** Applies [.mIconTint] to the notification icons. */
- private fun applyNotificationIconsTint() {
- for (i in 0 until notificationIcons!!.childCount) {
- val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, iconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
- }
- }
- updateAodIconColors()
- }
-
- private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
- val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
- var color = StatusBarIconView.NO_COLOR
- val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
- if (colorize) {
- color = DarkIconDispatcher.getTint(tintAreas, v, tint)
- }
- v.staticDrawableColor = color
- v.setDecorColor(tint)
- }
-
- private fun reloadAodColor() {
- aodIconTint =
- Utils.getColorAttrDefaultColor(
- context,
- R.attr.wallpaperTextColor,
- DEFAULT_AOD_ICON_COLOR
- )
- }
-
- private fun updateAodIconColors() {
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, aodIconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
- }
- }
- }
- }
+ override fun getHeight(): Int = unsupported
companion object {
- @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
-
val unsupported: Nothing
get() =
error(
- "Code path not supported when NOTIFICATION_ICON_CONTAINER_REFACTOR is disabled"
+ "Code path not supported when ${NotificationIconContainerRefactor.FLAG_NAME}" +
+ " is disabled"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 0d2f00a..c1f728a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,172 +15,465 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import android.content.res.Resources
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.graphics.Color
+import android.graphics.Rect
import android.view.View
-import androidx.annotation.DimenRes
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.collection.ArrayMap
+import androidx.lifecycle.lifecycleScope
import com.android.app.animation.Interpolators
+import com.android.internal.policy.SystemBarUtils
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.CrossFadeHelper
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
+/** Binds a view-model to a [NotificationIconContainer]. */
object NotificationIconContainerViewBinder {
+ @JvmStatic
fun bind(
view: NotificationIconContainer,
- viewModel: NotificationIconContainerViewModel,
+ viewModel: NotificationIconContainerShelfViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ viewStore: ShelfNotificationIconViewStore,
+ ): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ viewModel.icons.bindIcons(view, configuration, configurationController, viewStore)
+ }
+ }
+ }
+
+ @JvmStatic
+ fun bind(
+ view: NotificationIconContainer,
+ viewModel: NotificationIconContainerStatusBarViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ viewStore: StatusBarNotificationIconViewStore,
+ ): DisposableHandle {
+ val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
+ return view.repeatWhenAttached {
+ lifecycleScope.run {
+ launch {
+ viewModel.icons.bindIcons(
+ view,
+ configuration,
+ configurationController,
+ viewStore
+ )
+ }
+ launch { viewModel.iconColors.bindIconColors(view, contrastColorUtil) }
+ launch { viewModel.bindIsolatedIcon(view, viewStore) }
+ launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
+ }
+ }
+ }
+
+ @JvmStatic
+ fun bind(
+ view: NotificationIconContainer,
+ viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+ configuration: ConfigurationState,
configurationController: ConfigurationController,
dozeParameters: DozeParameters,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
+ viewStore: IconViewStore,
): DisposableHandle {
return view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
+ lifecycleScope.launch {
launch {
- viewModel.isDozing.collect { (isDozing, animate) ->
- val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
- view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
- viewModel.completeDozeAnimation()
- }
- }
+ viewModel.icons.bindIcons(
+ view,
+ configuration,
+ configurationController,
+ viewStore,
+ )
}
- // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+ launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
+ launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) }
+ // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
// view-binder
launch {
- val iconAppearTranslation =
- view.resources.getConfigAwareDimensionPixelSize(
- this,
- configurationController,
- R.dimen.shelf_appear_translation,
- )
- bindVisibility(
- viewModel,
+ viewModel.isVisible.bindIsVisible(
view,
+ configuration,
featureFlags,
screenOffAnimationController,
- iconAppearTranslation,
- ) {
- viewModel.completeVisibilityAnimation()
+ )
+ }
+ launch {
+ configuration
+ .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
+ .bindIconColors(view)
+ }
+ }
+ }
+ }
+
+ /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
+ private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
+ collect(view::setAnimationsEnabled)
+ }
+
+ /**
+ * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
+ * of the [children] of an [NotificationIconContainer].
+ */
+ private suspend fun Flow<NotificationIconColorLookup>.bindIconColors(
+ view: NotificationIconContainer,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+ .collect { iconLookup -> view.applyTint(iconLookup, contrastColorUtil) }
+ }
+
+ /**
+ * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
+ * of the [children] of an [NotificationIconContainer].
+ */
+ private suspend fun Flow<Int>.bindIconColors(view: NotificationIconContainer) {
+ collect { tint ->
+ view.children.filterIsInstance<StatusBarIconView>().forEach { icon ->
+ icon.staticDrawableColor = tint
+ icon.setDecorColor(tint)
+ }
+ }
+ }
+
+ private suspend fun Flow<AnimatedValue<Boolean>>.bindIsDozing(
+ view: NotificationIconContainer,
+ dozeParameters: DozeParameters,
+ ) {
+ collect { isDozing ->
+ if (isDozing.isAnimating) {
+ val animate = !dozeParameters.displayNeedsBlanking
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade = */ animate,
+ /* delay = */ 0,
+ /* endRunnable = */ isDozing::stopAnimating,
+ )
+ } else {
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade= */ false,
+ /* delay= */ 0,
+ )
+ }
+ }
+ }
+
+ private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
+ view: NotificationIconContainer,
+ viewStore: IconViewStore,
+ ) {
+ coroutineScope {
+ launch {
+ isolatedIconLocation.collect { location ->
+ view.setIsolatedIconLocation(location, true)
+ }
+ }
+ launch {
+ isolatedIcon.collect { iconInfo ->
+ val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
+ if (iconInfo.isAnimating) {
+ view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
+ } else {
+ view.showIconIsolated(iconView)
}
}
}
}
}
- private suspend fun bindVisibility(
- viewModel: NotificationIconContainerViewModel,
+
+ /** Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. */
+ private suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ viewStore: IconViewStore,
+ ): Unit = coroutineScope {
+ val iconSizeFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size_sp,
+ )
+ val iconHorizontalPaddingFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+ val statusBarHeightFlow: StateFlow<Int> =
+ stateFlow(changedSignals = configurationController.onConfigChanged) {
+ SystemBarUtils.getStatusBarHeight(view.context)
+ }
+ val layoutParams: Flow<FrameLayout.LayoutParams> =
+ combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) {
+ iconSize,
+ iconHPadding,
+ statusBarHeight,
+ ->
+ FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
+ }
+
+ launch {
+ layoutParams.collect { params: FrameLayout.LayoutParams ->
+ for (child in view.children) {
+ child.layoutParams = params
+ }
+ }
+ }
+
+ var prevIcons = NotificationIconsViewData()
+ sample(layoutParams, ::Pair).collect {
+ (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams),
+ ->
+ val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
+ prevIcons = iconsData
+
+ val replacingIcons =
+ iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) ->
+ viewStore.iconView(v.notifKey)?.statusBarIcon
+ }
+ view.setReplacingIcons(replacingIcons)
+
+ val childrenByNotifKey: Map<String, StatusBarIconView> =
+ view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) {
+ it.notification.key
+ }
+
+ iconsDiff.removed
+ .mapNotNull { key -> childrenByNotifKey[key] }
+ .forEach { child -> view.removeView(child) }
+
+ val toAdd = iconsDiff.added.mapNotNull { viewStore.iconView(it.notifKey) }
+ for ((i, sbiv) in toAdd.withIndex()) {
+ // The view might still be transiently added if it was just removed
+ // and added again
+ view.removeTransientView(sbiv)
+ view.addView(sbiv, i, layoutParams)
+ }
+
+ view.setChangingViewPositions(true)
+ // Re-sort notification icons
+ val childCount = view.childCount
+ for (i in 0 until childCount) {
+ val actual = view.getChildAt(i)
+ val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey)!!
+ if (actual === expected) {
+ continue
+ }
+ view.removeView(expected)
+ view.addView(expected, i)
+ }
+ view.setChangingViewPositions(false)
+
+ view.setReplacingIcons(null)
+ }
+ }
+
+ // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
+ // can be moved there and cleaned up.
+ private fun ViewGroup.applyTint(
+ iconColors: NotificationIconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ children
+ .filterIsInstance<StatusBarIconView>()
+ .filter { it.width != 0 }
+ .forEach { iv -> iv.updateTintForIcon(iconColors, contrastColorUtil) }
+ }
+
+ private fun StatusBarIconView.updateTintForIcon(
+ iconColors: NotificationIconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ val isPreL = java.lang.Boolean.TRUE == getTag(R.id.icon_is_pre_L)
+ val isColorized = !isPreL || NotificationUtils.isGrayscale(this, contrastColorUtil)
+ staticDrawableColor = iconColors.staticDrawableColor(viewBounds, isColorized)
+ setDecorColor(iconColors.tint)
+ }
+
+ private suspend fun Flow<AnimatedValue<Boolean>>.bindIsVisible(
+ view: NotificationIconContainer,
+ configuration: ConfigurationState,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- iconAppearTranslation: StateFlow<Int>,
- onAnimationEnd: () -> Unit,
- ) {
+ ): Unit = coroutineScope {
+ val iconAppearTranslation =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
- viewModel.isVisible.collect { (isVisible, animate) ->
+ collect { isVisible ->
view.animate().cancel()
+ val animatorListener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isVisible.stopAnimating()
+ }
+ }
when {
- !animate -> {
+ !isVisible.isAnimating -> {
view.alpha = 1f
if (!statusViewMigrated) {
view.translationY = 0f
}
- view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
- animateInIconTranslation(view, statusViewMigrated)
- if (isVisible) {
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ view.animateInIconTranslation(statusViewMigrated)
+ if (isVisible.value) {
+ CrossFadeHelper.fadeIn(view, animatorListener)
} else {
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
}
- !isVisible -> {
+ !isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
- animateInIconTranslation(view, statusViewMigrated)
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ view.animateInIconTranslation(statusViewMigrated)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
view.visibility != View.VISIBLE -> {
// No fading here, let's just appear the icons instead!
view.visibility = View.VISIBLE
view.alpha = 1f
- appearIcons(
- view,
+ view.appearIcons(
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconAppearTranslation.value,
statusViewMigrated,
+ animatorListener,
)
- onAnimationEnd()
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
- animateInIconTranslation(view, statusViewMigrated)
+ view.animateInIconTranslation(statusViewMigrated)
// We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ CrossFadeHelper.fadeIn(view, animatorListener)
}
}
}
}
- private fun appearIcons(
- view: View,
+ private fun View.appearIcons(
animate: Boolean,
iconAppearTranslation: Int,
statusViewMigrated: Boolean,
+ animatorListener: Animator.AnimatorListener,
) {
if (animate) {
if (!statusViewMigrated) {
- view.translationY = -iconAppearTranslation.toFloat()
+ translationY = -iconAppearTranslation.toFloat()
}
- view.alpha = 0f
- animateInIconTranslation(view, statusViewMigrated)
- view
- .animate()
+ alpha = 0f
+ animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
+ .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .setListener(animatorListener)
.start()
} else {
- view.alpha = 1.0f
+ alpha = 1.0f
if (!statusViewMigrated) {
- view.translationY = 0f
+ translationY = 0f
}
}
}
- private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
+ private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
if (!statusViewMigrated) {
- view
- .animate()
- .setInterpolator(Interpolators.DECELERATE_QUINT)
- .translationY(0f)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start()
+ animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
+ private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
+ setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+
+ /** External storage for [StatusBarIconView] instances. */
+ fun interface IconViewStore {
+ fun iconView(key: String): StatusBarIconView?
+ }
+
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+ @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
}
-fun Resources.getConfigAwareDimensionPixelSize(
- scope: CoroutineScope,
- configurationController: ConfigurationController,
- @DimenRes id: Int,
-): StateFlow<Int> =
- scope.stateFlow(
- changedSignals = configurationController.onDensityOrFontScaleChanged,
- getValue = { getDimensionPixelSize(id) }
- )
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.shelfIcon
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.aodIcon
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.statusBarIcon
+}
+
+private val View.viewBounds: Rect
+ get() {
+ val tmpArray = intArrayOf(0, 0)
+ getLocationOnScreen(tmpArray)
+ return Rect(
+ /* left = */ tmpArray[0],
+ /* top = */ tmpArray[1],
+ /* right = */ left + width,
+ /* bottom = */ top + height,
+ )
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
new file mode 100644
index 0000000..97d1e1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.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.statusbar.notification.icon.ui.viewmodel
+
+import android.graphics.Rect
+
+/**
+ * Lookup the colors to use for the notification icons based on the bounds of the icon container. A
+ * result of `null` indicates that no color changes should be applied.
+ */
+fun interface NotificationIconColorLookup {
+ fun iconColors(viewBounds: Rect): NotificationIconColors?
+}
+
+/** Colors to apply to notification icons. */
+interface NotificationIconColors {
+
+ /** A tint to apply to the icons. */
+ val tint: Int
+
+ /**
+ * Returns the color to be applied to an icon, based on that icon's view bounds and whether or
+ * not the notification icon is colorized.
+ */
+ fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 3289a3c..611ed89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FeatureFlagsClassic
@@ -25,6 +26,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.pairwise
@@ -32,9 +34,9 @@
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -47,17 +49,16 @@
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val featureFlags: FeatureFlagsClassic,
+ iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
-) : NotificationIconContainerViewModel {
+) {
- private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
- private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
- override val animationsEnabled: Flow<Boolean> =
+ /** Are changes to the icon container animated? */
+ val animationsEnabled: Flow<Boolean> =
combine(
shadeInteractor.isShadeTouchable,
keyguardInteractor.isKeyguardVisible,
@@ -65,7 +66,8 @@
panelTouchesEnabled && isKeyguardVisible
}
- override val isDozing: Flow<AnimatedValue<Boolean>> =
+ /** Should icons be rendered in "dozing" mode? */
+ val isDozing: Flow<AnimatedValue<Boolean>> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
// Determine if we're dozing based on the most recent transition
.map { step: TransitionStep ->
@@ -80,9 +82,10 @@
AnimatableEvent(isDozing, animate)
}
.distinctUntilChanged()
- .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+ .toAnimatedValueFlow()
- override val isVisible: Flow<AnimatedValue<Boolean>> =
+ /** Is the icon container visible? */
+ val isVisible: Flow<AnimatedValue<Boolean>> =
combine(
keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE },
deviceEntryInteractor.isBypassEnabled,
@@ -90,46 +93,51 @@
isPulseExpandingAnimated(),
) {
onKeyguard: Boolean,
- bypassEnabled: Boolean,
- (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
- (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+ isBypassEnabled: Boolean,
+ notifsFullyHidden: AnimatedValue<Boolean>,
+ pulseExpanding: AnimatedValue<Boolean>,
->
- val isAnimating = isAnimatingHide || isAnimatingPulse
when {
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
// animation is playing, in which case we want them to be visible if we're
// animating in the AOD UI and will be switching to KEYGUARD shortly.
!onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
- AnimatedValue(false, isAnimating = false)
- // If we're bypassing, then we're visible
- bypassEnabled -> AnimatedValue(true, isAnimating)
- // If we are pulsing (and not bypassing), then we are hidden
- pulseExpanding -> AnimatedValue(false, isAnimating)
- // If notifs are fully gone, then we're visible
- notifsFullyHidden -> AnimatedValue(true, isAnimating)
- // Otherwise, we're hidden
- else -> AnimatedValue(false, isAnimating)
+ AnimatedValue.NotAnimating(false)
+ else ->
+ zip(notifsFullyHidden, pulseExpanding) {
+ areNotifsFullyHidden,
+ isPulseExpanding,
+ ->
+ when {
+ // If we're bypassing, then we're visible
+ isBypassEnabled -> true
+ // If we are pulsing (and not bypassing), then we are hidden
+ isPulseExpanding -> false
+ // If notifs are fully gone, then we're visible
+ areNotifsFullyHidden -> true
+ // Otherwise, we're hidden
+ else -> false
+ }
+ }
}
}
.distinctUntilChanged()
- override fun completeDozeAnimation() {
- onDozeAnimationComplete.tryEmit(Unit)
- }
-
- override fun completeVisibilityAnimation() {
- onVisAnimationComplete.tryEmit(Unit)
- }
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
+ iconsInteractor.aodNotifs.map { entries ->
+ NotificationIconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+ )
+ }
/** Is there an expanded pulse, are we animating in response? */
private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
return notificationsKeyguardInteractor.isPulseExpanding
.pairwise(initialValue = null)
// If pulsing changes, start animating, unless it's the first emission
- .map { (prev, expanding) ->
- AnimatableEvent(expanding!!, startAnimating = prev != null)
- }
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
+ .toAnimatedValueFlow()
}
/** Are notifications completely hidden from view, are we animating in response? */
@@ -151,10 +159,14 @@
// We only want the appear animations to happen when the notifications
// get fully hidden, since otherwise the un-hide animation overlaps.
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
- else -> fullyHidden!!
+ else -> fullyHidden
}
- AnimatableEvent(fullyHidden!!, animate)
+ AnimatableEvent(fullyHidden, animate)
}
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .toAnimatedValueFlow()
+ }
+
+ private class IconColorsImpl(override val tint: Int) : NotificationIconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index c44a2b6..1560106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,18 +15,22 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** View-model for the overflow row of notification icons displayed in the notification shade. */
-class NotificationIconContainerShelfViewModel @Inject constructor() :
- NotificationIconContainerViewModel {
- override val animationsEnabled: Flow<Boolean> = flowOf(true)
- override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override fun completeDozeAnimation() {}
- override fun completeVisibilityAnimation() {}
+class NotificationIconContainerShelfViewModel
+@Inject
+constructor(
+ interactor: NotificationIconsInteractor,
+) {
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
+ interactor.filteredNotifSet().map { entries ->
+ NotificationIconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 035687a..53631e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -15,22 +15,39 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed in the status bar, */
class NotificationIconContainerStatusBarViewModel
@Inject
constructor(
+ darkIconInteractor: DarkIconInteractor,
+ iconsInteractor: StatusBarNotificationIconsInteractor,
+ headsUpIconInteractor: HeadsUpNotificationIconInteractor,
keyguardInteractor: KeyguardInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
shadeInteractor: ShadeInteractor,
-) : NotificationIconContainerViewModel {
- override val animationsEnabled: Flow<Boolean> =
+) {
+
+ /** Are changes to the icon container animated? */
+ val animationsEnabled: Flow<Boolean> =
combine(
shadeInteractor.isShadeTouchable,
keyguardInteractor.isKeyguardShowing,
@@ -38,8 +55,67 @@
panelTouchesEnabled && !isKeyguardShowing
}
- override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override fun completeDozeAnimation() {}
- override fun completeVisibilityAnimation() {}
+ /** The colors with which to display the notification icons. */
+ val iconColors: Flow<NotificationIconColorLookup> =
+ combine(
+ darkIconInteractor.tintAreas,
+ darkIconInteractor.tintColor,
+ // Included so that tints are re-applied after entries are changed.
+ notificationsInteractor.notifications,
+ ) { areas, tint, _ ->
+ NotificationIconColorLookup { viewBounds: Rect ->
+ if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ IconColorsImpl(tint, areas)
+ } else {
+ null
+ }
+ }
+ }
+
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
+ iconsInteractor.statusBarNotifs.map { entries ->
+ NotificationIconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+ )
+ }
+
+ /** An Icon to show "isolated" in the IconContainer. */
+ val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
+ headsUpIconInteractor.isolatedNotification
+ .pairwise(initialValue = null)
+ .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) {
+ (prev, isolatedNotif),
+ (iconsViewData, shadeExpansion),
+ ->
+ val iconInfo =
+ isolatedNotif?.let {
+ iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+ }
+ val animate =
+ when {
+ isolatedNotif == prev -> false
+ isolatedNotif == null || prev == null -> shadeExpansion == 0f
+ else -> false
+ }
+ AnimatableEvent(iconInfo, animate)
+ }
+ .toAnimatedValueFlow()
+
+ /** Location to show an isolated icon, if there is one. */
+ val isolatedIconLocation: Flow<Rect> =
+ headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+
+ private class IconColorsImpl(
+ override val tint: Int,
+ private val areas: Collection<Rect>,
+ ) : NotificationIconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
+ return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ tint
+ } else {
+ DarkIconDispatcher.DEFAULT_ICON_TINT
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
deleted file mode 100644
index 65eb220..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-
-import com.android.systemui.util.ui.AnimatedValue
-import kotlinx.coroutines.flow.Flow
-
-/**
- * View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and
- * AOD.
- */
-interface NotificationIconContainerViewModel {
- /** Are changes to the icon container animated? */
- val animationsEnabled: Flow<Boolean>
-
- /** Should icons be rendered in "dozing" mode? */
- val isDozing: Flow<AnimatedValue<Boolean>>
-
- /** Is the icon container visible? */
- val isVisible: Flow<AnimatedValue<Boolean>>
-
- /**
- * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
- */
- fun completeDozeAnimation()
-
- /**
- * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
- */
- fun completeVisibilityAnimation()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
new file mode 100644
index 0000000..867be84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.icon.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import androidx.collection.ArrayMap
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+
+/** Encapsulates the collection of notification icons present on the device. */
+data class NotificationIconsViewData(
+ /** Icons that are visible in the container. */
+ val visibleKeys: List<NotificationIconInfo> = emptyList(),
+ /** Keys of icons that are "behind" the overflow dot. */
+ val collapsedKeys: Set<String> = emptySet(),
+ /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */
+ val forceShowDot: Boolean = false,
+) {
+ /** The difference between two [NotificationIconsViewData]s. */
+ data class Diff(
+ /** Icons added in the newer dataset. */
+ val added: List<NotificationIconInfo> = emptyList(),
+ /** Icons removed from the older dataset. */
+ val removed: List<String> = emptyList(),
+ /**
+ * Groups whose icon was replaced with a single new notification icon. The key of the [Map]
+ * is the notification group key, and the value is the new icon.
+ *
+ * Specifically, this models a difference where the older dataset had notification groups
+ * with a single icon in the set, and the newer dataset has a single, different icon for the
+ * same group. A view binder can use this information for special animations for this
+ * specific change.
+ */
+ val groupReplacements: Map<String, NotificationIconInfo> = emptyMap(),
+ )
+
+ companion object {
+ /**
+ * Returns an [NotificationIconsViewData.Diff] calculated from a [new] and [previous][prev]
+ * [NotificationIconsViewData] state.
+ */
+ fun computeDifference(
+ new: NotificationIconsViewData,
+ prev: NotificationIconsViewData
+ ): Diff {
+ val added: List<NotificationIconInfo> =
+ new.visibleKeys.filter {
+ it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val removed: List<NotificationIconInfo> =
+ prev.visibleKeys.filter {
+ it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val groupsToShow: Set<IconGroupInfo> =
+ new.visibleKeys.asSequence().map { it.groupInfo }.toSet()
+ val replacements: ArrayMap<String, NotificationIconInfo> =
+ removed
+ .asSequence()
+ .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
+ .groupBy { it.groupInfo.groupKey }
+ .mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
+ vs.takeIf { it.size == 1 }?.get(0)
+ }
+ return Diff(added, removed.map { it.notifKey }, replacements)
+ }
+ }
+}
+
+/** An Icon, and keys for unique identification. */
+data class NotificationIconInfo(
+ val sourceIcon: Icon,
+ val notifKey: String,
+ val groupKey: String,
+)
+
+/**
+ * Construct an [NotificationIconInfo] out of an [ActiveNotificationModel], or return `null` if one
+ * cannot be created due to missing information.
+ */
+fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): NotificationIconInfo? {
+ return sourceIcon?.let {
+ groupKey?.let { groupKey ->
+ NotificationIconInfo(
+ sourceIcon = sourceIcon,
+ notifKey = key,
+ groupKey = groupKey,
+ )
+ }
+ }
+}
+
+private val NotificationIconInfo.groupInfo: IconGroupInfo
+ get() = IconGroupInfo(sourceIcon, groupKey)
+
+private data class IconGroupInfo(
+ val sourceIcon: Icon,
+ val groupKey: String,
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as IconGroupInfo
+
+ if (groupKey != other.groupKey) return false
+ return sourceIcon.sameAs(other.sourceIcon)
+ }
+
+ override fun hashCode(): Int {
+ var result = groupKey.hashCode()
+ result = 31 * result + sourceIcon.type.hashCode()
+ when (sourceIcon.type) {
+ Icon.TYPE_BITMAP,
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.bitmap.hashCode()
+ }
+ Icon.TYPE_DATA -> {
+ result = 31 * result + sourceIcon.dataLength.hashCode()
+ result = 31 * result + sourceIcon.dataOffset.hashCode()
+ }
+ Icon.TYPE_RESOURCE -> {
+ result = 31 * result + sourceIcon.resId.hashCode()
+ result = 31 * result + sourceIcon.resPackage.hashCode()
+ }
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.uriString.hashCode()
+ }
+ }
+ return result
+ }
+}
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 4a823a4..2fffd37 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 @@
})
}
- 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 @@
})
}
- 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/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 6ec9dbe..b0155f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -180,4 +180,9 @@
* Add a component that can suppress visual interruptions.
*/
void addSuppressor(NotificationInterruptSuppressor suppressor);
+
+ /**
+ * Remove a component that can suppress visual interruptions.
+ */
+ void removeSuppressor(NotificationInterruptSuppressor suppressor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 3819843..301ddbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -175,6 +175,11 @@
}
@Override
+ public void removeSuppressor(NotificationInterruptSuppressor suppressor) {
+ mSuppressors.remove(suppressor);
+ }
+
+ @Override
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
@@ -505,7 +510,7 @@
if (entry.getRanking().getLockscreenVisibilityOverride()
== Notification.VISIBILITY_PRIVATE) {
- if (log) mLogger.logNoPulsingNotificationHidden(entry);
+ if (log) mLogger.logNoPulsingNotificationHiddenOverride(entry);
return false;
}
@@ -536,7 +541,7 @@
}
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/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
index ebdeded..d7f0baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -58,6 +58,10 @@
wrapped.addSuppressor(suppressor)
}
+ override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+ wrapped.removeSuppressor(suppressor)
+ }
+
override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index 454ba02..920bbe9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -60,6 +60,13 @@
fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor)
/**
+ * Removes a [component][suppressor] that can suppress visual interruptions.
+ *
+ * @param[suppressor] the suppressor to remove
+ */
+ fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor)
+
+ /**
* Decides whether a [notification][entry] should display as heads-up or not, but does not log
* that decision.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
new file mode 100644
index 0000000..4ef80e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.interruption
+
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * A reason why visual interruptions might be suppressed.
+ *
+ * @see VisualInterruptionCondition
+ * @see VisualInterruptionFilter
+ */
+enum class VisualInterruptionType {
+ /* HUN when awake */
+ PEEK,
+
+ /* HUN when dozing */
+ PULSE,
+
+ /* Bubble */
+ BUBBLE
+}
+
+/**
+ * A reason why visual interruptions might be suppressed.
+ *
+ * @see VisualInterruptionCondition
+ * @see VisualInterruptionFilter
+ */
+sealed interface VisualInterruptionSuppressor {
+ /** The type(s) of interruption that this suppresses. */
+ val types: Set<VisualInterruptionType>
+
+ /** A human-readable string to be logged to explain why this suppressed an interruption. */
+ val reason: String
+
+ /** An optional UiEvent ID to be recorded when this suppresses an interruption. */
+ val uiEventId: UiEventEnum?
+}
+
+/** A reason why visual interruptions might be suppressed regardless of the notification. */
+abstract class VisualInterruptionCondition(
+ override val types: Set<VisualInterruptionType>,
+ override val reason: String,
+ override val uiEventId: UiEventEnum? = null
+) : VisualInterruptionSuppressor {
+ /** @return true if these interruptions should be suppressed right now. */
+ abstract fun shouldSuppress(): Boolean
+}
+
+/** A reason why visual interruptions might be suppressed based on the notification. */
+abstract class VisualInterruptionFilter(
+ override val types: Set<VisualInterruptionType>,
+ override val reason: String,
+ override val uiEventId: UiEventEnum? = null
+) : VisualInterruptionSuppressor {
+ /**
+ * @param entry the notification to consider suppressing
+ * @return true if these interruptions should be suppressed for this notification right now
+ */
+ abstract fun shouldSuppress(entry: NotificationEntry): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 661768d..8f1e59d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -121,7 +121,7 @@
private void updateColors() {
mNormalColor = Utils.getColorAttrDefaultColor(mContext,
- com.android.internal.R.attr.colorSurface);
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh);
mTintedRippleColor = mContext.getColor(
R.color.notification_ripple_tinted_color);
mNormalRippleColor = mContext.getColor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index 892a635..da8c4dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -28,7 +28,6 @@
import androidx.annotation.ColorInt;
-import com.android.internal.util.ContrastColorUtil;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -98,8 +97,8 @@
private void resolveThemeTextColors() {
try (TypedArray ta = mContext.getTheme().obtainStyledAttributes(
android.R.style.Theme_DeviceDefault_DayNight, new int[]{
- android.R.attr.textColorPrimary,
- android.R.attr.textColorSecondary
+ com.android.internal.R.attr.materialColorOnSurface,
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
})) {
if (ta != null) {
mPrimaryTextColor = ta.getColor(0, mPrimaryTextColor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 62b268b..14593cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -65,10 +65,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -336,10 +336,11 @@
Drawable person = mIconFactory.getBaseIconDrawable(mShortcutInfo);
if (person == null) {
person = mContext.getDrawable(R.drawable.ic_person).mutate();
- TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
- int colorAccent = ta.getColor(0, 0);
+ TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{com.android.internal.R.attr.materialColorPrimary});
+ int colorPrimary = ta.getColor(0, 0);
ta.recycle();
- person.setTint(colorAccent);
+ person.setTint(colorPrimary);
}
ImageView image = findViewById(R.id.conversation_icon);
image.setImageDrawable(person);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index ff5b9cb..cdf178e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -326,7 +326,8 @@
if (customBackgroundColor != 0) {
return customBackgroundColor;
}
- return Utils.getColorAttr(mView.getContext(), android.R.attr.colorBackground)
+ return Utils.getColorAttr(mView.getContext(),
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh)
.getDefaultColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
new file mode 100644
index 0000000..78370ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import android.graphics.drawable.Icon
+
+/** Model for entries in the notification stack. */
+data class ActiveNotificationModel(
+ /** Notification key associated with this entry. */
+ val key: String,
+ /** Notification group key associated with this entry. */
+ val groupKey: String?,
+ /** Is this entry in the ambient / minimized section (lowest priority)? */
+ val isAmbient: Boolean,
+ /**
+ * Is this entry dismissed? This is `true` when the user has dismissed the notification in the
+ * UI, but `NotificationManager` has not yet signalled to us that it has received the dismissal.
+ */
+ val isRowDismissed: Boolean,
+ /** Is this entry in the silent section? */
+ val isSilent: Boolean,
+ /**
+ * Does this entry represent a conversation, the last message of which was from a remote input
+ * reply?
+ */
+ val isLastMessageFromReply: Boolean,
+ /** Is this entry suppressed from appearing in the status bar as an icon? */
+ val isSuppressedFromStatusBar: Boolean,
+ /** Is this entry actively pulsing on AOD or bypassed-keyguard? */
+ val isPulsing: Boolean,
+ /** Icon to display on AOD. */
+ val aodIcon: Icon?,
+ /** Icon to display in the notification shelf. */
+ val shelfIcon: Icon?,
+ /** Icon to display in the status bar. */
+ val statusBarIcon: Icon?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
new file mode 100644
index 0000000..dee609c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the NotificationIconContainer refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationIconContainerRefactor {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR
+
+ /** Is the refactor enabled? */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsIconContainerRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index b92c51f..d35e4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -19,19 +19,23 @@
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
@@ -75,14 +79,25 @@
fun bind(
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
falsingManager: FalsingManager,
- featureFlags: FeatureFlags,
notificationIconAreaController: NotificationIconAreaController,
+ shelfIconViewStore: ShelfNotificationIconViewStore,
) {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
- // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
- notificationIconAreaController.setShelfIcons(shelfIcons)
+ if (NotificationIconContainerRefactor.isEnabled) {
+ NotificationIconContainerViewBinder.bind(
+ shelfIcons,
+ viewModel.icons,
+ configuration,
+ configurationController,
+ shelfIconViewStore,
+ )
+ } else {
+ notificationIconAreaController.setShelfIcons(shelfIcons)
+ }
repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 5ca8b53..64b5b62c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import javax.inject.Inject
@@ -31,6 +32,7 @@
constructor(
private val interactor: NotificationShelfInteractor,
activatableViewModel: ActivatableNotificationViewModel,
+ val icons: NotificationIconContainerShelfViewModel,
) : ActivatableNotificationViewModel by activatableViewModel {
/** Is the shelf allowed to be clickable when it has content? */
val isClickable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a929e4f..0236fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1362,7 +1362,7 @@
Resources.Theme theme = new ContextThemeWrapper(mContext,
com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
try (TypedArray ta = theme.obtainStyledAttributes(
- new int[]{com.android.internal.R.attr.colorAccent})) {
+ new int[]{com.android.internal.R.attr.materialColorPrimary})) {
color = ta.getColor(0, color);
}
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index e4d96c3..6bb9573 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -67,18 +67,6 @@
void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer);
/**
- * Generate an animation for an added child view.
- * @param child The view to be added.
- * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
- */
- void generateAddAnimation(ExpandableView child, boolean fromMoreCard);
-
- /**
- * Generate a child order changed event.
- */
- void generateChildOrderChangedEvent();
-
- /**
* Returns the number of children in the NotificationListContainer.
*
* @return the number of children in the NotificationListContainer
@@ -187,21 +175,6 @@
default void bindRow(ExpandableNotificationRow row) {}
/**
- * Does this list contain a given view. True by default is fine, since we only ask this if the
- * view has a parent.
- */
- default boolean containsView(View v) {
- return true;
- }
-
- /**
- * Tells the container that an animation is about to expand it.
- */
- default void setWillExpand(boolean willExpand) {}
-
- void setNotificationActivityStarter(NotificationActivityStarter notificationActivityStarter);
-
- /**
* @return the start location where we start clipping notifications.
*/
default int getTopClippingStartLocation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index fd064ee..cfc433a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -244,10 +244,10 @@
}
}
- fun setHeaderForegroundColor(@ColorInt color: Int) {
- peopleHeaderView?.setForegroundColor(color)
- silentHeaderView?.setForegroundColor(color)
- alertingHeaderView?.setForegroundColor(color)
+ fun setHeaderForegroundColors(@ColorInt onSurface: Int, @ColorInt onSurfaceVariant: Int) {
+ peopleHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+ silentHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+ alertingHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dba93d9..8babcc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -107,6 +107,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -324,7 +325,6 @@
private NotificationsController mNotificationsController;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
- private boolean mGenerateChildOrderChangedEvent;
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
@@ -344,7 +344,7 @@
private boolean mAnimateNextBackgroundTop;
private boolean mAnimateNextBackgroundBottom;
private boolean mAnimateNextSectionBoundsChange;
- private int mBgColor;
+ private @ColorInt int mBgColor;
private float mDimAmount;
private ValueAnimator mDimAnimator;
private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
@@ -647,8 +647,8 @@
mSections = mSectionsManager.createSectionsForBuckets();
mAmbientState = Dependency.get(AmbientState.class);
- mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
- .getDefaultColor();
+ mBgColor = Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -695,7 +695,9 @@
super.onFinishInflate();
inflateEmptyShadeView();
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ }
}
/**
@@ -730,9 +732,11 @@
}
void reinflateViews() {
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ updateFooter();
+ }
inflateEmptyShadeView();
- updateFooter();
mSectionsManager.reinflateViews();
}
@@ -747,7 +751,7 @@
@VisibleForTesting
public void updateFooter() {
- if (mFooterView == null) {
+ if (mFooterView == null || mController == null) {
return;
}
// TODO: move this logic to controller, which will invoke updateFooterView directly
@@ -786,8 +790,8 @@
}
void updateBgColor() {
- mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
- .getDefaultColor();
+ mBgColor = Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
updateBackgroundDimming();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
@@ -3144,10 +3148,6 @@
requestChildrenUpdate();
}
- public boolean containsView(View v) {
- return v.getParent() == this;
- }
-
public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
// Modify the clipping for launching notifications
mLaunchAnimationParams = params;
@@ -3410,11 +3410,6 @@
mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
- if (mGenerateChildOrderChangedEvent) {
- mAnimationEvents.add(new AnimationEvent(null,
- AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
- mGenerateChildOrderChangedEvent = false;
- }
}
private void generateChildAdditionEvents() {
@@ -4426,14 +4421,19 @@
}
/**
- * Update colors of "dismiss" and "empty shade" views.
+ * Update colors of section headers, shade footer, and empty shade views.
*/
void updateDecorViews() {
- final @ColorInt int textColor =
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
- mSectionsManager.setHeaderForegroundColor(textColor);
+ final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.materialColorOnSurface);
+ final @ColorInt int onSurfaceVariant = Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.materialColorOnSurfaceVariant);
+
+ mSectionsManager.setHeaderForegroundColors(onSurface, onSurfaceVariant);
+
mFooterView.updateColors();
- mEmptyShadeView.setTextColor(textColor);
+
+ mEmptyShadeView.setTextColors(onSurface, onSurfaceVariant);
}
void goToFullShade(long delay) {
@@ -4556,7 +4556,8 @@
return mFooterView != null && mFooterView.isHistoryShown();
}
- void setFooterView(@NonNull FooterView footerView) {
+ /** Bind the {@link FooterView} to the NSSL. */
+ public void setFooterView(@NonNull FooterView footerView) {
int index = -1;
if (mFooterView != null) {
index = indexOfChild(mFooterView);
@@ -4567,6 +4568,16 @@
if (mManageButtonClickListener != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
}
+ mFooterView.setClearAllButtonClickListener(v -> {
+ if (mFooterClearAllListener != null) {
+ mFooterClearAllListener.onClearAll();
+ }
+ clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
+ });
+ if (FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
}
public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4629,7 +4640,9 @@
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
- mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ }
}
@VisibleForTesting
@@ -4764,14 +4777,6 @@
info.setClassName(ScrollView.class.getName());
}
- public void generateChildOrderChangedEvent() {
- if (mIsExpanded && mAnimationsEnabled) {
- mGenerateChildOrderChangedEvent = true;
- mNeedsAnimation = true;
- requestChildrenUpdate();
- }
- }
-
public int getContainerChildCount() {
return getChildCount();
}
@@ -5388,15 +5393,9 @@
@VisibleForTesting
protected void inflateFooterView() {
+ FooterViewRefactor.assertInLegacyMode();
FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_footer, this, false);
- footerView.setClearAllButtonClickListener(v -> {
- if (mFooterClearAllListener != null) {
- mFooterClearAllListener.onClearAll();
- }
- clearNotifications(ROWS_ALL, true /* closeShade */);
- footerView.setSecondaryVisible(false /* visible */, true /* animate */);
- });
setFooterView(footerView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a70815..21efd63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -59,10 +59,11 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
@@ -106,6 +107,8 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -114,13 +117,14 @@
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -194,7 +198,7 @@
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
- private final NotificationListInteractor mNotificationListInteractor;
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final KeyguardTransitionRepository mKeyguardTransitionRepo;
private NotificationStackScrollLayout mView;
@@ -206,12 +210,14 @@
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final RefactorFlag mShelfRefactor;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final ConfigurationState mConfigurationState;
+ private final ShelfNotificationIconViewStore mShelfIconViewStore;
private View mLongPressedView;
@@ -662,19 +668,21 @@
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
- NotificationListInteractor notificationListInteractor,
+ SeenNotificationsInteractor seenNotificationsInteractor,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
NotificationIconAreaController notifIconAreaController,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ ConfigurationState configurationState,
+ ShelfNotificationIconViewStore shelfIconViewStore) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mStackStateLogger = stackLogger;
@@ -715,7 +723,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
- mNotificationListInteractor = notificationListInteractor;
+ mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
@@ -724,6 +732,8 @@
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mConfigurationState = configurationState;
+ mShelfIconViewStore = shelfIconViewStore;
mView.passSplitShadeStateController(splitShadeStateController);
updateResources();
setUpView();
@@ -832,7 +842,10 @@
mViewModel.ifPresent(
vm -> NotificationListViewBinder
- .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+ .bind(mView, vm, mConfigurationState, mConfigurationController,
+ mFalsingManager,
+ mNotifIconAreaController,
+ mShelfIconViewStore));
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
@@ -1725,28 +1738,11 @@
}
@Override
- public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
- mView.generateAddAnimation(child, fromMoreCard);
- }
-
- @Override
- public void generateChildOrderChangedEvent() {
- mView.generateChildOrderChangedEvent();
- }
-
- @Override
public int getContainerChildCount() {
return mView.getContainerChildCount();
}
@Override
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {
- NotificationStackScrollLayoutController.this
- .setNotificationActivityStarter(notificationActivityStarter);
- }
-
- @Override
public int getTopClippingStartLocation() {
return mView.getTopClippingStartLocation();
}
@@ -1841,16 +1837,6 @@
}
@Override
- public boolean containsView(View v) {
- return mView.containsView(v);
- }
-
- @Override
- public void setWillExpand(boolean willExpand) {
- mView.setWillExpand(willExpand);
- }
-
- @Override
public void dumpPipeline(@NonNull PipelineDumper d) {
d.dump("NotificationStackScrollLayoutController.this",
NotificationStackScrollLayoutController.this);
@@ -2006,7 +1992,7 @@
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
mView.setHasFilteredOutSeenNotifications(
- mNotificationListInteractor.getHasFilteredOutSeenNotifications().getValue());
+ mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 722c28c..5c1149b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -132,8 +132,8 @@
mLabelView.setText(resId);
}
- void setForegroundColor(@ColorInt int color) {
- mLabelView.setTextColor(color);
- mClearAllButton.setImageTintList(ColorStateList.valueOf(color));
+ void setForegroundColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) {
+ mLabelView.setTextColor(onSurface);
+ mClearAllButton.setImageTintList(ColorStateList.valueOf(onSurfaceVariant));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
deleted file mode 100644
index f6ed8c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Repository for information about the current notification list. */
-@SysUISingleton
-class NotificationListRepository @Inject constructor() {
- private val _hasFilteredOutSeenNotifications = MutableStateFlow(false)
- val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
- _hasFilteredOutSeenNotifications.asStateFlow()
-
- fun setHasFilteredOutSeenNotifications(value: Boolean) {
- _hasFilteredOutSeenNotifications.value = value
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index dee3973..95b467f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,14 +17,21 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.reinflateAndBindLatest
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.traceSection
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
@@ -32,9 +39,11 @@
fun bind(
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
falsingManager: FalsingManager,
- featureFlags: FeatureFlags,
iconAreaController: NotificationIconAreaController,
+ shelfIconViewStore: ShelfNotificationIconViewStore,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -42,10 +51,27 @@
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
+ configuration,
+ configurationController,
falsingManager,
- featureFlags,
- iconAreaController
+ iconAreaController,
+ shelfIconViewStore,
)
view.setShelf(shelf)
+
+ viewModel.footer.ifPresent { footerViewModel ->
+ // The footer needs to be re-inflated every time the theme or the font size changes.
+ view.repeatWhenAttached {
+ configuration.reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ view,
+ attachToRoot = false,
+ ) { footerView: FooterView ->
+ traceSection("bind FooterView") {
+ FooterViewBinder.bind(footerView, footerViewModel)
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 11f68e0..261371d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import dagger.Module
import dagger.Provides
@@ -28,6 +29,7 @@
/** ViewModel for the list of notifications. */
class NotificationListViewModel(
val shelf: NotificationShelfViewModel,
+ val footer: Optional<FooterViewModel>,
)
@Module
@@ -36,12 +38,33 @@
@Provides
@SysUISingleton
fun maybeProvideViewModel(
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
shelfViewModel: Provider<NotificationShelfViewModel>,
- ): Optional<NotificationListViewModel> =
- if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
- Optional.of(NotificationListViewModel(shelfViewModel.get()))
+ footerViewModel: Provider<FooterViewModel>,
+ ): Optional<NotificationListViewModel> {
+ return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ Optional.of(
+ NotificationListViewModel(
+ shelfViewModel.get(),
+ Optional.of(footerViewModel.get())
+ )
+ )
+ } else {
+ Optional.of(NotificationListViewModel(shelfViewModel.get(), Optional.empty()))
+ }
} else {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ throw IllegalStateException(
+ "The com.android.systemui.notifications_footer_view_refactor flag requires " +
+ "the notification_shelf_refactor flag to be enabled. First disable the " +
+ "footer flag using `adb shell device_config put systemui " +
+ "com.android.systemui.notifications_footer_view_refactor false`, then " +
+ "enable the notification_shelf_refactor flag in Flag Flipper. " +
+ "Afterwards, you can try re-enabling the footer refactor flag via adb."
+ )
+ }
Optional.empty()
}
+ }
}
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 07d3a1c..2d125462 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.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.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 @@
)
}
+ 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 @@
!willLaunchResolverActivity &&
shouldAnimateLaunch(isActivityIntent = true)
val animController =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
@@ -547,12 +560,18 @@
)
}
- /** 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 @@
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.
+ // 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()
}
- if (intent.isActivity) {
- assistManagerLazy.get().hideAssist()
- }
- 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 @@
// 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 @@
* 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 @@
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 daa4f18..cbe9d4b 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 @@
// 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 7cd32f9..897bb42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -202,11 +202,11 @@
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;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -237,8 +237,6 @@
import dalvik.annotation.optimization.NeverCompile;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
@@ -250,6 +248,8 @@
import javax.inject.Named;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* A class handling initialization and coordination between some of the key central surfaces in
* System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -296,7 +296,6 @@
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private final NotificationListContainer mNotifListContainer;
- private final NotificationExpansionRepository mNotificationExpansionRepository;
private boolean mIsShortcutListSearchEnabled;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -650,7 +649,6 @@
Lazy<NotificationPresenter> notificationPresenterLazy,
Lazy<NotificationActivityStarter> notificationActivityStarterLazy,
NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider,
- NotificationExpansionRepository notificationExpansionRepository,
DozeParameters dozeParameters,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -759,7 +757,6 @@
mPresenterLazy = notificationPresenterLazy;
mNotificationActivityStarterLazy = notificationActivityStarterLazy;
mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider;
- mNotificationExpansionRepository = notificationExpansionRepository;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
@@ -813,8 +810,6 @@
mShadeExpansionStateManager.addExpansionListener(shadeExpansionListener);
shadeExpansionListener.onPanelExpansionChanged(currentState);
- mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
-
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mActivityLaunchAnimator = activityLaunchAnimator;
@@ -1401,20 +1396,6 @@
}
}
- @VisibleForTesting
- void onShadeExpansionFullyChanged(Boolean isExpanded) {
- if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
- if (DEBUG) {
- Log.v(TAG, "clearing notification effects from Height");
- }
- clearNotificationEffects();
- }
-
- if (!isExpanded) {
- mRemoteInputManager.onPanelCollapsed();
- }
- }
-
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -2530,7 +2511,7 @@
&& mFingerprintManager.get() != null
&& mFingerprintManager.get().isPowerbuttonFps()
&& mKeyguardUpdateMonitor
- .getCachedIsUnlockWithFingerprintPossible(
+ .isUnlockWithFingerprintPossible(
mUserTracker.getUserId())
&& !touchToUnlockAnytime;
if (DEBUG_WAKEUP_DELAY) {
@@ -2639,7 +2620,7 @@
!mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
mShadeSurface.setTouchAndAnimationDisabled(disabled);
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (!NotificationIconContainerRefactor.isEnabled()) {
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
}
@@ -3106,7 +3087,9 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ }
}
@Override
@@ -3124,7 +3107,9 @@
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- mNotificationIconAreaController.onThemeChanged();
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ mNotificationIconAreaController.onThemeChanged();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index d3d11ea..600d4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,7 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -47,18 +48,18 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Assert;
-import dagger.Lazy;
-
import java.util.ArrayList;
import javax.inject.Inject;
+import dagger.Lazy;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
@@ -82,6 +83,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final HeadsUpManager mHeadsUpManager;
+ private final FeatureFlagsClassic mFeatureFlags;
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -107,6 +109,7 @@
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
DeviceProvisionedController deviceProvisionedController,
+ FeatureFlagsClassic featureFlags,
HeadsUpManager headsUpManager, BatteryController batteryController,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -130,6 +133,7 @@
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAssistManagerLazy = assistManagerLazy;
mDozeScrimController = dozeScrimController;
+ mFeatureFlags = featureFlags;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPulseExpansionHandler = pulseExpansionHandler;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -173,8 +177,13 @@
void fireNotificationPulse(NotificationEntry entry) {
Runnable pulseSuppressedListener = () -> {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
+ } else {
+ entry.setPulseSuppressed(true);
+ mNotificationIconAreaController.updateAodNotificationIcons();
+ }
};
Assert.isMainThread();
for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c493eeda..beeee1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -38,7 +39,9 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
@@ -104,6 +107,8 @@
};
private boolean mAnimationsEnabled = true;
private final KeyguardStateController mKeyguardStateController;
+ private final FeatureFlagsClassic mFeatureFlags;
+ private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
@VisibleForTesting
@Inject
@@ -122,6 +127,8 @@
NotificationRoundnessManager notificationRoundnessManager,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
+ FeatureFlagsClassic featureFlags,
+ HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
@@ -139,6 +146,8 @@
mStackScrollerController = stackScrollerController;
mShadeViewController = shadeViewController;
+ mFeatureFlags = featureFlags;
+ mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
mOperatorNameViewOptional = operatorNameViewOptional;
@@ -170,6 +179,9 @@
mHeadsUpManager.addListener(this);
mView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ updateIsolatedIconLocation(true);
+ }
mWakeUpCoordinator.addListener(this);
getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -185,6 +197,9 @@
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
+ }
mWakeUpCoordinator.removeListener(this);
getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -193,8 +208,13 @@
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
- mNotificationIconAreaController.setIsolatedIconLocation(
- mView.getIconDrawingRect(), requireStateUpdate);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mHeadsUpNotificationIconInteractor
+ .setIsolatedIconLocation(mView.getIconDrawingRect());
+ } else {
+ mNotificationIconAreaController.setIsolatedIconLocation(
+ mView.getIconDrawingRect(), requireStateUpdate);
+ }
}
@Override
@@ -230,9 +250,14 @@
setShown(true);
animateIsolation = !isExpanded();
}
- updateIsolatedIconLocation(false /* requireUpdate */);
- mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
- : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+ newEntry == null ? null : newEntry.getKey());
+ } else {
+ updateIsolatedIconLocation(false /* requireUpdate */);
+ mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
+ : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index f4862c7..3a95e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -34,7 +34,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -105,7 +106,8 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// Constructor:
@Inject
- public HeadsUpManagerPhone(@NonNull final Context context,
+ public HeadsUpManagerPhone(
+ @NonNull final Context context,
HeadsUpManagerLogger logger,
StatusBarStateController statusBarStateController,
KeyguardBypassController bypassController,
@@ -115,7 +117,8 @@
@Main Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
- ShadeExpansionStateManager shadeExpansionStateManager) {
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor) {
super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
@@ -136,8 +139,7 @@
updateResources();
}
});
-
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -230,7 +232,7 @@
mTrackingHeadsUp = trackingHeadsUp;
}
- private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ private void onShadeOrQsExpanded(Boolean isExpanded) {
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 63591d7..b0183d3 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 @@
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 @@
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 109e77e..3329844 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 @@
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/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index f9856b0..dd32434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -448,7 +448,7 @@
}
}
replacingIcons.removeAll(duplicates);
- hostLayout.setReplacingIcons(replacingIcons);
+ hostLayout.setReplacingIconsLegacy(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
@@ -481,7 +481,7 @@
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
- hostLayout.setReplacingIcons(null);
+ hostLayout.setReplacingIconsLegacy(null);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
index 0079f7c..2823b28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
@@ -28,7 +28,7 @@
*/
interface NotificationIconAreaController {
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
- fun setupAodIcons(aodIcons: NotificationIconContainer)
+ fun setupAodIcons(aodIcons: NotificationIconContainer?)
fun setupShelf(notificationShelfController: NotificationShelfController)
fun setShelfIcons(icons: NotificationIconContainer)
fun onDensityOrFontScaleChanged(context: Context)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
index d1ddd51..ba69370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
@@ -15,9 +15,8 @@
*/
package com.android.systemui.statusbar.phone
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import dagger.Module
import dagger.Provides
import javax.inject.Provider
@@ -26,11 +25,10 @@
object NotificationIconAreaControllerModule {
@Provides
fun provideNotificationIconAreaControllerImpl(
- featureFlags: FeatureFlags,
legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
): NotificationIconAreaController =
- if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled) {
newProvider.get()
} else {
legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index b15c0fd..efb8e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -42,6 +42,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -156,7 +157,8 @@
private int mIconSize;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
- private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
+ private ArrayMap<String, StatusBarIcon> mReplacingIcons;
+ private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIconsLegacy;
// Keep track of the last visible icon so collapsed container can report on its location
private IconState mLastVisibleIconState;
private IconState mFirstVisibleIconState;
@@ -167,6 +169,7 @@
private final int[] mAbsolutePosition = new int[2];
private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
+ private Runnable mIsolatedIconAnimationEndRunnable;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -339,23 +342,29 @@
}
private boolean isReplacingIcon(View child) {
- if (mReplacingIcons == null) {
- return false;
- }
if (!(child instanceof StatusBarIconView)) {
return false;
}
StatusBarIconView iconView = (StatusBarIconView) child;
Icon sourceIcon = iconView.getSourceIcon();
String groupKey = iconView.getNotification().getGroupKey();
- ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
- if (statusBarIcons != null) {
- StatusBarIcon replacedIcon = statusBarIcons.get(0);
- if (sourceIcon.sameAs(replacedIcon.icon)) {
- return true;
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mReplacingIcons == null) {
+ return false;
}
+ StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+ return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
+ } else {
+ if (mReplacingIconsLegacy == null) {
+ return false;
+ }
+ ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
+ if (statusBarIcons != null) {
+ StatusBarIcon replacedIcon = statusBarIcons.get(0);
+ return sourceIcon.sameAs(replacedIcon.icon);
+ }
+ return false;
}
- return false;
}
@Override
@@ -681,14 +690,36 @@
mAnimationsEnabled = enabled;
}
- public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
+ mReplacingIconsLegacy = replacingIcons;
+ }
+
+ public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mReplacingIcons = replacingIcons;
}
+ @Deprecated
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
if (animated) {
- mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ showIconIsolatedAnimated(icon, null);
+ } else {
+ showIconIsolated(icon);
}
+ }
+
+ public void showIconIsolatedAnimated(StatusBarIconView icon,
+ @Nullable Runnable onAnimationEnd) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+ mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ mIsolatedIconAnimationEndRunnable = onAnimationEnd;
+ showIconIsolated(icon);
+ }
+
+ public void showIconIsolated(StatusBarIconView icon) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mIsolatedIcon = icon;
updateState();
}
@@ -813,6 +844,11 @@
animationProperties = UNISOLATION_PROPERTY;
animationProperties.setDelay(
mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
+ Consumer<Property> endAction = getEndAction();
+ if (endAction != null) {
+ animationProperties.setAnimationEndAction(endAction);
+ animationProperties.setAnimationCancelAction(endAction);
+ }
} else {
animationProperties = UNISOLATION_PROPERTY_OTHERS;
animationProperties.setDelay(
@@ -836,6 +872,18 @@
needsCannedAnimation = false;
}
+ @Nullable
+ private Consumer<Property> getEndAction() {
+ if (mIsolatedIconAnimationEndRunnable == null) return null;
+ final Runnable endRunnable = mIsolatedIconAnimationEndRunnable;
+ return prop -> {
+ endRunnable.run();
+ if (mIsolatedIconAnimationEndRunnable == endRunnable) {
+ mIsolatedIconAnimationEndRunnable = null;
+ }
+ };
+ }
+
@Override
public void initFrom(View view) {
super.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 744d70e..3e753a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1505,8 +1505,9 @@
private void updateThemeColors() {
if (mScrimBehind == null) return;
int background = Utils.getColorAttr(mScrimBehind.getContext(),
- android.R.attr.colorBackgroundFloating).getDefaultColor();
- int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
+ com.android.internal.R.attr.materialColorSurfaceDim).getDefaultColor();
+ int accent = Utils.getColorAttr(mScrimBehind.getContext(),
+ com.android.internal.R.attr.materialColorPrimary).getDefaultColor();
mColors.setMainColor(background);
mColors.setSecondaryColor(accent);
final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
index 4d9de09..fa6d279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -3,12 +3,15 @@
import android.app.StatusBarManager
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.util.concurrency.DelayableExecutor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import java.io.PrintWriter
import javax.inject.Inject
@@ -25,14 +28,14 @@
*/
@SysUISingleton
class StatusBarHideIconsForBouncerManager @Inject constructor(
- private val commandQueue: CommandQueue,
- @Main private val mainExecutor: DelayableExecutor,
- statusBarWindowStateController: StatusBarWindowStateController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
- dumpManager: DumpManager
+ @Application private val scope: CoroutineScope,
+ private val commandQueue: CommandQueue,
+ @Main private val mainExecutor: DelayableExecutor,
+ statusBarWindowStateController: StatusBarWindowStateController,
+ val shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager
) : Dumpable {
// State variables set by external classes.
- private var panelExpanded: Boolean = false
private var isOccluded: Boolean = false
private var bouncerShowing: Boolean = false
private var topAppHidesStatusBar: Boolean = false
@@ -49,10 +52,9 @@
statusBarWindowStateController.addListener {
state -> setStatusBarStateAndTriggerUpdate(state)
}
- shadeExpansionStateManager.addFullExpansionListener { isExpanded ->
- if (panelExpanded != isExpanded) {
- panelExpanded = isExpanded
- updateHideIconsForBouncer(animate = false)
+ scope.launch {
+ shadeInteractor.isAnyExpanded.collect {
+ updateHideIconsForBouncer(false)
}
}
}
@@ -101,7 +103,7 @@
topAppHidesStatusBar &&
isOccluded &&
(statusBarWindowHidden || bouncerShowing)
- val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing
+ val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing
val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard
if (hideIconsForBouncer != shouldHideIconsForBouncer) {
hideIconsForBouncer = shouldHideIconsForBouncer
@@ -125,9 +127,13 @@
}
}
+ private fun isShadeOrQsExpanded(): Boolean {
+ return shadeInteractor.isAnyExpanded.value
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("---- State variables set externally ----")
- pw.println("panelExpanded=$panelExpanded")
+ pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}")
pw.println("isOccluded=$isOccluded")
pw.println("bouncerShowing=$bouncerShowing")
pw.println("topAppHideStatusBar=$topAppHidesStatusBar")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a5cd062..07e2571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -52,7 +52,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -80,7 +80,7 @@
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor;
private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
@@ -107,7 +107,7 @@
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
- NotificationsInteractor notificationsInteractor,
+ NotificationAlertsInteractor notificationAlertsInteractor,
LockscreenShadeTransitionController shadeTransitionController,
PowerInteractor powerInteractor,
CommandQueue commandQueue,
@@ -127,7 +127,7 @@
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
- mNotificationsInteractor = notificationsInteractor;
+ mNotificationAlertsInteractor = notificationAlertsInteractor;
mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
@@ -303,7 +303,7 @@
@Override
public boolean suppressInterruptions(NotificationEntry entry) {
- return !mNotificationsInteractor.areNotificationAlertsEnabled();
+ return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index ba73c10..6d8ec44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -39,6 +39,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -86,8 +87,9 @@
ConfigurationController configurationController,
HeadsUpManager headsUpManager,
ShadeExpansionStateManager shadeExpansionStateManager,
+ ShadeInteractor shadeInteractor,
Provider<SceneInteractor> sceneInteractor,
- Provider<JavaAdapter> javaAdapter,
+ JavaAdapter javaAdapter,
SceneContainerFlags sceneContainerFlags,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
PrimaryBouncerInteractor primaryBouncerInteractor,
@@ -126,12 +128,12 @@
});
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
if (sceneContainerFlags.isEnabled()) {
- javaAdapter.get().alwaysCollectFlow(
+ javaAdapter.alwaysCollectFlow(
sceneInteractor.get().isVisible(),
- this::onShadeExpansionFullyChanged);
+ this::onShadeOrQsExpanded);
}
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -151,7 +153,7 @@
pw.println(mTouchableRegion);
}
- private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ private void onShadeOrQsExpanded(Boolean isExpanded) {
if (isExpanded != mIsStatusBarExpanded) {
mIsStatusBarExpanded = isExpanded;
if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index d7f3b07..71e25e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -77,11 +77,15 @@
configurationController?.removeCallback(onConfigChanged)
}
+ /** Can be overridden by subclasses to receive config changed events. */
+ open fun onConfigurationChanged() {}
+
private val onConfigChanged =
object : ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
super.onConfigChanged(newConfig)
setupEdgeToEdge()
+ onConfigurationChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
index a033e1d..66ac17e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.ColorInt;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
@@ -28,6 +29,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import com.android.wm.shell.animation.Interpolators;
@@ -49,8 +51,9 @@
}
void updateColor() {
- int textColor = getResources().getColor(R.color.notif_pill_text, mContext.getTheme());
- setTextColor(textColor);
+ final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
+ com.android.internal.R.attr.materialColorOnSurface);
+ setTextColor(onSurface);
setBackground(getResources().getDrawable(R.drawable.rounded_bg_full, mContext.getTheme()));
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
index 9b0c3fa..9645c69 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.statusbar.phone.data
-package com.android.systemui.common.ui.data.repository
-
-import dagger.Binds
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepositoryModule
import dagger.Module
-@Module
-interface CommonRepositoryModule {
- @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-}
+@Module(includes = [DarkIconRepositoryModule::class]) object StatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
new file mode 100644
index 0000000..ba377497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.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.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Dark-mode state for tinting icons. */
+interface DarkIconRepository {
+ val darkState: StateFlow<DarkChange>
+}
+
+@SysUISingleton
+class DarkIconRepositoryImpl
+@Inject
+constructor(
+ darkIconDispatcher: SysuiDarkIconDispatcher,
+) : DarkIconRepository {
+ override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+}
+
+@Module
+interface DarkIconRepositoryModule {
+ @Binds fun bindImpl(impl: DarkIconRepositoryImpl): DarkIconRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
new file mode 100644
index 0000000..246645e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** States pertaining to calculating colors for icons in dark mode. */
+class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
+ val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
+ /**
+ * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
+ */
+ val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
+ val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+}
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 63c022c..3921e69 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
@@ -38,9 +38,12 @@
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -52,7 +55,12 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -66,6 +74,7 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -74,6 +83,8 @@
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;
@@ -83,7 +94,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
@@ -128,7 +138,7 @@
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
@@ -142,6 +152,11 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
+ private final ConfigurationState mConfigurationState;
+ private final ConfigurationController mConfigurationController;
+ private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
+ private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -211,9 +226,9 @@
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
ShadeExpansionStateManager shadeExpansionStateManager,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
StatusBarIconController statusBarIconController,
- StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
+ DarkIconManager.Factory darkIconManagerFactory,
CollapsedStatusBarViewModel collapsedStatusBarViewModel,
CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
@@ -228,8 +243,12 @@
@Main Executor mainExecutor,
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor
- ) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
+ ConfigurationState configurationState,
+ ConfigurationController configurationController,
+ StatusBarNotificationIconViewStore statusBarIconViewStore,
+ DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
@@ -254,18 +273,53 @@
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mStatusBarIconsViewModel = statusBarIconsViewModel;
+ mConfigurationState = configurationState;
+ mConfigurationController = configurationController;
+ mStatusBarIconViewStore = statusBarIconViewStore;
+ mDemoModeController = demoModeController;
}
+ private final DemoMode mDemoModeCallback = new DemoMode() {
+ @Override
+ public List<String> demoCommands() {
+ return List.of(DemoMode.COMMAND_NOTIFICATIONS);
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (mNotificationIconAreaInner == null) return;
+ String visible = args.getString("visible");
+ if ("false".equals(visible)) {
+ mNotificationIconAreaInner.setVisibility(View.INVISIBLE);
+ } else {
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ if (mNotificationIconAreaInner == null) return;
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mDemoModeController.addCallback(mDemoModeCallback);
+ }
}
@Override
public void onDestroy() {
super.onDestroy();
mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mDemoModeController.removeCallback(mDemoModeCallback);
+ }
}
@Override
@@ -405,14 +459,28 @@
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
- ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
- mNotificationIconAreaInner =
- mNotificationIconAreaController.getNotificationInnerAreaView();
- if (mNotificationIconAreaInner.getParent() != null) {
- ((ViewGroup) mNotificationIconAreaInner.getParent())
- .removeView(mNotificationIconAreaInner);
+ ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ mNotificationIconAreaInner =
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ NotificationIconContainer notificationIcons =
+ notificationIconArea.requireViewById(R.id.notificationIcons);
+ NotificationIconContainerViewBinder.bind(
+ notificationIcons,
+ mStatusBarIconsViewModel,
+ mConfigurationState,
+ mConfigurationController,
+ mStatusBarIconViewStore);
+ } else {
+ mNotificationIconAreaInner =
+ mNotificationIconAreaController.getNotificationInnerAreaView();
+ if (mNotificationIconAreaInner.getParent() != null) {
+ ((ViewGroup) mNotificationIconAreaInner.getParent())
+ .removeView(mNotificationIconAreaInner);
+ }
+ notificationIconArea.addView(mNotificationIconAreaInner);
}
- notificationIconArea.addView(mNotificationIconAreaInner);
updateNotificationIconAreaAndCallChip(/* animate= */ false);
}
@@ -526,7 +594,7 @@
// 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/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
index 21acfb4..0a2bbe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -13,7 +13,8 @@
*/
package com.android.systemui.statusbar.policy
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import android.content.res.Configuration
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -23,14 +24,47 @@
* @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
*/
val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
- get() =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- val listener =
- object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- trySend(Unit)
- }
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ trySend(Unit)
}
- addCallback(listener)
- awaitClose { removeCallback(listener) }
- }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
+
+/**
+ * A [Flow] that emits whenever the theme has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onThemeChanged
+ */
+val ConfigurationController.onThemeChanged: Flow<Unit>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ trySend(Unit)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
+
+/**
+ * A [Flow] that emits whenever the configuration has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onConfigChanged
+ */
+val ConfigurationController.onConfigChanged: Flow<Configuration>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
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 8929e02..52133ee 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 @@
/**
* If there are faces enrolled and user enabled face auth on keyguard.
*/
- default boolean isFaceAuthEnabled() {
+ default boolean isFaceEnrolled() {
return false;
}
@@ -265,9 +265,9 @@
/**
* 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 c624518..8cc7e7d2 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 @@
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 @@
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 @@
|| (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 @@
}
@Override
- public boolean isFaceAuthEnabled() {
- return mFaceAuthEnabled;
+ public boolean isFaceEnrolled() {
+ return mFaceEnrolled;
}
@Override
@@ -416,7 +416,7 @@
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/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index ceed81a..4864fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -130,7 +130,7 @@
private ImageView mDelete;
private ImageView mDeleteBg;
private boolean mColorized;
- private int mTint;
+ private int mLastBackgroundColor;
private boolean mResetting;
@Nullable
private RevealParams mRevealParams;
@@ -181,10 +181,9 @@
mEditorActionHandler = new EditorActionHandler();
mUiEventLogger = Dependency.get(UiEventLogger.class);
TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
- com.android.internal.R.attr.colorAccent,
- com.android.internal.R.attr.colorSurface,
+ com.android.internal.R.attr.materialColorSurfaceDim,
});
- mTint = ta.getColor(0, 0);
+ mLastBackgroundColor = ta.getColor(0, 0);
ta.recycle();
}
@@ -210,9 +209,9 @@
* @param backgroundColor colorized notification color
*/
public void setBackgroundTintColor(final int backgroundColor, boolean colorized) {
- if (colorized == mColorized && backgroundColor == mTint) return;
+ if (colorized == mColorized && backgroundColor == mLastBackgroundColor) return;
mColorized = colorized;
- mTint = backgroundColor;
+ mLastBackgroundColor = backgroundColor;
final int editBgColor;
final int deleteBgColor;
final int deleteFgColor;
@@ -237,8 +236,8 @@
hintColor = mContext.getColor(R.color.remote_input_hint);
deleteFgColor = textColor.getDefaultColor();
try (TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
- com.android.internal.R.attr.colorSurfaceHighlight,
- com.android.internal.R.attr.colorSurfaceVariant
+ com.android.internal.R.attr.materialColorSurfaceDim,
+ com.android.internal.R.attr.materialColorSurfaceVariant
})) {
editBgColor = ta.getColor(0, backgroundColor);
deleteBgColor = ta.getColor(1, Color.GRAY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index eaae0f0..fa6ea4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -30,10 +30,13 @@
import android.util.Log
import android.view.View.MeasureSpec
import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Dependency
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.ViewController
import com.android.systemui.util.time.SystemClock
import java.text.FieldPosition
@@ -83,7 +86,7 @@
class VariableDateViewController(
private val systemClock: SystemClock,
private val broadcastDispatcher: BroadcastDispatcher,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeInteractor: ShadeInteractor,
private val shadeLogger: ShadeLogger,
private val timeTickHandler: Handler,
view: VariableDateView
@@ -174,8 +177,11 @@
broadcastDispatcher.registerReceiver(intentReceiver, filter,
HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
-
- shadeExpansionStateManager.addQsExpansionFractionListener(::onQsExpansionFractionChanged)
+ mView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ shadeInteractor.qsExpansion.collect(::onQsExpansionFractionChanged)
+ }
+ }
post(::updateClock)
mView.onAttach(onMeasureListener)
}
@@ -183,7 +189,6 @@
override fun onViewDetached() {
dateFormat = null
mView.onAttach(null)
- shadeExpansionStateManager.removeQsExpansionFractionListener(::onQsExpansionFractionChanged)
broadcastDispatcher.unregisterReceiver(intentReceiver)
}
@@ -237,18 +242,18 @@
class Factory @Inject constructor(
private val systemClock: SystemClock,
private val broadcastDispatcher: BroadcastDispatcher,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeInteractor: ShadeInteractor,
private val shadeLogger: ShadeLogger,
@Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
) {
fun create(view: VariableDateView): VariableDateViewController {
return VariableDateViewController(
- systemClock,
- broadcastDispatcher,
- shadeExpansionStateManager,
- shadeLogger,
- handler,
- view
+ systemClock,
+ broadcastDispatcher,
+ shadeInteractor,
+ shadeLogger,
+ handler,
+ view
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 0a44bda..f0c7be6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -4,27 +4,74 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Tracing
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.systemui.util.tracing.TraceContextElement
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Qualifier
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/** Key associated with a [Boolean] flag that enables or disables the coroutine tracing feature. */
+@Qualifier
+annotation class CoroutineTracingEnabledKey
+
+/**
+ * Same as [@Application], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedApplication
+
+/**
+ * Same as [@Background], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedBackground
/** Providers for various coroutines-related constructs. */
@Module
-object CoroutinesModule {
+class CoroutinesModule {
@Provides
@SysUISingleton
@Application
fun applicationScope(
- @Main dispatcher: CoroutineDispatcher,
- ): CoroutineScope = CoroutineScope(dispatcher)
+ @Main dispatcherContext: CoroutineContext,
+ ): CoroutineScope = CoroutineScope(dispatcherContext)
+
+ @Provides
+ @SysUISingleton
+ @UnflaggedApplication
+ fun unflaggedApplicationScope(): CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
@Provides
@SysUISingleton
@Main
+ @Deprecated(
+ "Use @Main CoroutineContext instead",
+ ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+ @Provides
+ @SysUISingleton
+ @Main
+ fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.Main.immediate + tracingCoroutineContext
+ }
+
/**
* Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where
* X is the number of CPU cores available.
@@ -37,5 +84,42 @@
@Provides
@SysUISingleton
@Background
+ @Deprecated(
+ "Use @Background CoroutineContext instead",
+ ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+
+ @Provides
+ @Background
+ @SysUISingleton
+ fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.IO + tracingCoroutineContext
+ }
+
+ @Provides
+ @UnflaggedBackground
+ @SysUISingleton
+ fun unflaggedBackgroundCoroutineContext(): CoroutineContext {
+ return Dispatchers.IO
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Provides
+ @Tracing
+ @SysUISingleton
+ fun tracingCoroutineContext(
+ @CoroutineTracingEnabledKey enableTracing: Boolean
+ ): CoroutineContext = if (enableTracing) TraceContextElement() else EmptyCoroutineContext
+
+ companion object {
+ @[Provides CoroutineTracingEnabledKey]
+ fun provideIsCoroutineTracingEnabledKey(featureFlags: FeatureFlagsClassic): Boolean {
+ return if (featureFlags.isEnabled(Flags.COROUTINE_TRACING)) {
+ coroutineTracingIsEnabled = true
+ true
+ } else false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
new file mode 100644
index 0000000..909a18be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.awaitCancellation
+
+/**
+ * Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for
+ * garbage collection.
+ *
+ * This utility is useful if you want to bind a [repeatWhenAttached] invocation to the lifetime of a
+ * coroutine, such that cancelling the coroutine cleans up the handle. For example:
+ * ```
+ * myFlow.collectLatest { value ->
+ * val disposableHandle = myView.repeatWhenAttached { doStuff() }
+ * doSomethingWith(value)
+ * // un-bind when done
+ * disposableHandle.awaitCancellationThenDispose()
+ * }
+ * ```
+ */
+suspend fun DisposableHandle.awaitCancellationThenDispose() {
+ try {
+ awaitCancellation()
+ } finally {
+ dispose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index b3834f5..8fe57e11 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -61,10 +61,10 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- initialValue: T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ initialValue: S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = pairwiseBy(getInitialValue = { initialValue }, transform)
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -75,10 +75,16 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- getInitialValue: suspend () -> T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ getInitialValue: suspend () -> S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = flow {
+ var previousValue: S = getInitialValue()
+ collect { newVal ->
+ emit(transform(previousValue, newVal))
+ previousValue = newVal
+ }
+}
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -86,7 +92,7 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
+fun <T> Flow<T>.pairwise(): Flow<WithPrev<T, T>> = pairwiseBy(::WithPrev)
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
@@ -94,10 +100,11 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(initialValue: T): Flow<WithPrev<T>> = pairwiseBy(initialValue, ::WithPrev)
+fun <S, T : S> Flow<T>.pairwise(initialValue: S): Flow<WithPrev<S, T>> =
+ pairwiseBy(initialValue, ::WithPrev)
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
-data class WithPrev<T>(val previousValue: T, val newValue: T)
+data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
@@ -265,112 +272,127 @@
* immediately invoke [getValue] to establish its initial value.
*/
inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<Unit>,
+ changedSignals: Flow<*>,
crossinline getValue: () -> T,
): StateFlow<T> =
changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
inline fun <T1, T2, T3, T4, T5, T6, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
): Flow<R> {
- return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) {
- args: Array<*> ->
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
+ ->
@Suppress("UNCHECKED_CAST")
transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6
)
}
}
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- flow7: Flow<T7>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
): Flow<R> {
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
args: Array<*> ->
@Suppress("UNCHECKED_CAST")
transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6,
- args[6] as T7
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7
)
}
}
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- flow7: Flow<T7>,
- flow8: Flow<T8>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
): Flow<R> {
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
args: Array<*> ->
@Suppress("UNCHECKED_CAST")
transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6,
- args[6] as T7,
- args[7] as T8
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[7] as T8
)
}
}
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- flow7: Flow<T7>,
- flow8: Flow<T8>,
- flow9: Flow<T9>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ flow9: Flow<T9>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
): Flow<R> {
return kotlinx.coroutines.flow.combine(
- flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9
+ flow,
+ flow2,
+ flow3,
+ flow4,
+ flow5,
+ flow6,
+ flow7,
+ flow8,
+ flow9
) { args: Array<*> ->
@Suppress("UNCHECKED_CAST")
transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6,
- args[6] as T7,
- args[6] as T8,
- args[6] as T9,
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[6] as T8,
+ args[6] as T9,
)
}
-}
\ No newline at end of file
+}
+
+/**
+ * Returns a [Flow] that immediately emits [Unit] when started, then emits from the given upstream
+ * [Flow] as normal.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
new file mode 100644
index 0000000..41cd95b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.util.kotlin
+
+/** Like [mapValues], but discards `null` values returned from [block]. */
+fun <K, V, R> Map<K, V>.mapValuesNotNull(block: (Map.Entry<K, V>) -> R?): Map<K, R> = buildMap {
+ this@mapValuesNotNull.mapValuesNotNullTo(this, block)
+}
+
+/** Like [mapValuesTo], but discards `null` values returned from [block]. */
+fun <K, V, R, M : MutableMap<in K, in R>> Map<out K, V>.mapValuesNotNullTo(
+ destination: M,
+ block: (Map.Entry<K, V>) -> R?,
+): M {
+ for (entry in this) {
+ block(entry)?.also { destination.put(entry.key, it) }
+ }
+ return destination
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
index 51d2afa..1112d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -17,26 +17,58 @@
package com.android.systemui.util.ui
+import com.android.systemui.util.ui.AnimatedValue.Animating
+import com.android.systemui.util.ui.AnimatedValue.NotAnimating
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
/**
* A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
* [value] [isAnimating] in the UI.
*/
-data class AnimatedValue<T>(
- val value: T,
- val isAnimating: Boolean,
-)
+sealed interface AnimatedValue<out T> {
+
+ /** A [state][value] that is not actively animating in the UI. */
+ data class NotAnimating<out T>(val value: T) : AnimatedValue<T>
+
+ /**
+ * A [state][value] that is actively animating in the UI. Invoking [onStopAnimating] will signal
+ * the source of the state to stop animating.
+ */
+ data class Animating<out T>(
+ val value: T,
+ val onStopAnimating: () -> Unit,
+ ) : AnimatedValue<T>
+}
+
+/** The state held in this [AnimatedValue]. */
+inline val <T> AnimatedValue<T>.value: T
+ get() =
+ when (this) {
+ is Animating -> value
+ is NotAnimating -> value
+ }
+
+/** Returns whether or not this [AnimatedValue] is animating or not. */
+inline val <T> AnimatedValue<T>.isAnimating: Boolean
+ get() = this is Animating<T>
+
+/**
+ * If this [AnimatedValue] [isAnimating], then signal that the animation should be stopped.
+ * Otherwise, do nothing.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun AnimatedValue<*>.stopAnimating() {
+ if (this is Animating) onStopAnimating()
+}
/**
* An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
* whether or not this event should start an animation.
*/
-data class AnimatableEvent<T>(
+data class AnimatableEvent<out T>(
val value: T,
val startAnimating: Boolean,
)
@@ -47,16 +79,87 @@
* [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
* [AnimatedValue.isAnimating] will flip to `false`.
*/
-fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
- completionEvents: Flow<Any?>,
-): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
- emit(AnimatedValue(value, isAnimating = startAnimating))
- if (startAnimating) {
- // Wait for a completion now that we've started animating
- completionEvents
- .map { Unit } // replace the event so that it's never `null`
- .firstOrNull() // `null` indicates an empty flow
- // emit the new state if the flow was not empty.
- ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(): Flow<AnimatedValue<T>> =
+ transformLatest { (value, startAnimating) ->
+ if (startAnimating) {
+ val onCompleted = CompletableDeferred<Unit>()
+ emit(Animating(value) { onCompleted.complete(Unit) })
+ // Wait for a completion now that we've started animating
+ onCompleted.await()
+ }
+ emit(NotAnimating(value))
+ }
+
+/**
+ * Zip two [AnimatedValue]s together into a single [AnimatedValue], using [block] to combine the
+ * [value]s of each.
+ *
+ * If either [AnimatedValue] [isAnimating], then the result is also animating. Invoking
+ * [stopAnimating] on the result is equivalent to invoking [stopAnimating] on each input.
+ */
+inline fun <A, B, Z> zip(
+ valueA: AnimatedValue<A>,
+ valueB: AnimatedValue<B>,
+ block: (A, B) -> Z,
+): AnimatedValue<Z> {
+ val zippedValue = block(valueA.value, valueB.value)
+ return when (valueA) {
+ is Animating ->
+ when (valueB) {
+ is Animating ->
+ Animating(zippedValue) {
+ valueA.onStopAnimating()
+ valueB.onStopAnimating()
+ }
+ is NotAnimating -> Animating(zippedValue, valueA.onStopAnimating)
+ }
+ is NotAnimating ->
+ when (valueB) {
+ is Animating -> Animating(zippedValue, valueB.onStopAnimating)
+ is NotAnimating -> NotAnimating(zippedValue)
+ }
}
}
+
+/**
+ * Flattens a nested [AnimatedValue], the result of which holds the [value] of the inner
+ * [AnimatedValue].
+ *
+ * If either the outer or inner [AnimatedValue] [isAnimating], then the flattened result is also
+ * animating. Invoking [stopAnimating] on the result is equivalent to invoking [stopAnimating] on
+ * both the outer and inner values.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> AnimatedValue<AnimatedValue<T>>.flatten(): AnimatedValue<T> = flatMap { it }
+
+/**
+ * Returns an [AnimatedValue], the [value] of which is the result of the given [value] applied to
+ * [block].
+ */
+inline fun <A, B> AnimatedValue<A>.map(block: (A) -> B): AnimatedValue<B> =
+ when (this) {
+ is Animating -> Animating(block(value), ::stopAnimating)
+ is NotAnimating -> NotAnimating(block(value))
+ }
+
+/**
+ * Returns an [AnimatedValue] from the result of [block] being invoked on the [value] original
+ * [AnimatedValue].
+ *
+ * If either the input [AnimatedValue] or the result of [block] [isAnimating], then the flattened
+ * result is also animating. Invoking [stopAnimating] on the result is equivalent to invoking
+ * [stopAnimating] on both values.
+ */
+inline fun <A, B> AnimatedValue<A>.flatMap(block: (A) -> AnimatedValue<B>): AnimatedValue<B> =
+ when (this) {
+ is NotAnimating -> block(value)
+ is Animating ->
+ when (val inner = block(value)) {
+ is Animating ->
+ Animating(inner.value) {
+ onStopAnimating()
+ inner.onStopAnimating()
+ }
+ is NotAnimating -> Animating(inner.value, onStopAnimating)
+ }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
new file mode 100644
index 0000000..d3653b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.view
+
+import android.view.View
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
+ * updates. New emissions lead to the previous binding call being cancelled if not completed.
+ * Dispose of the [DisposableHandle] returned by [bind] when done.
+ */
+suspend fun <T : View> Flow<T>.bindLatest(bind: (T) -> DisposableHandle?) {
+ this.collectLatest { view ->
+ val disposableHandle = bind(view)
+ disposableHandle?.awaitCancellationThenDispose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0ff308e..280c66a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -99,7 +99,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -256,7 +255,6 @@
private CaptionsToggleImageButton mODICaptionsIcon;
private View mSettingsView;
private ImageButton mSettingsIcon;
- private FrameLayout mZenIcon;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
@@ -633,7 +631,6 @@
mRinger = mDialog.findViewById(R.id.ringer);
if (mRinger != null) {
mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
- mZenIcon = mRinger.findViewById(R.id.dnd_icon);
}
mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
@@ -847,7 +844,6 @@
if (stream == STREAM_ACCESSIBILITY) {
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
- row.dndIcon = row.view.findViewById(R.id.dnd_icon);
row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
@@ -1791,27 +1787,13 @@
}
/**
- * Toggles enable state of views in a VolumeRow (not including seekbar or icon)
- * Hides/shows zen icon
- * @param enable whether to enable volume row views and hide dnd icon
- */
- private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
- boolean showDndIcon = !enable;
- row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE);
- }
-
- /**
* Toggles enable state of footer/ringer views
- * Hides/shows zen icon
- * @param enable whether to enable ringer views and hide dnd icon
+ * @param enable whether to enable ringer views
*/
private void enableRingerViewsH(boolean enable) {
if (mRingerIcon != null) {
mRingerIcon.setEnabled(enable);
}
- if (mZenIcon != null) {
- mZenIcon.setVisibility(enable ? GONE : VISIBLE);
- }
}
private void trimObsoleteH() {
@@ -1937,9 +1919,11 @@
// update icon
final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
final int iconRes;
- if (isRingVibrate) {
+ if (zenMuted) {
+ iconRes = com.android.internal.R.drawable.ic_qs_dnd;
+ } else if (isRingVibrate) {
iconRes = R.drawable.ic_volume_ringer_vibrate;
- } else if (isRingSilent || zenMuted) {
+ } else if (isRingSilent) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
if (isVoiceCallStream) {
@@ -2011,7 +1995,6 @@
if (zenMuted) {
row.tracking = false;
}
- enableVolumeRowViewsH(row, !zenMuted);
// update slider
final boolean enableSlider = !zenMuted;
@@ -2582,7 +2565,6 @@
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
- private FrameLayout dndIcon;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index 0b3c3b4..c4b43e1 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -17,11 +17,16 @@
import android.content.Context
import android.content.res.Resources
+import android.testing.TestableContext
+import android.testing.TestableResources
import com.android.systemui.FakeSystemUiModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -33,15 +38,29 @@
FakeSystemUiModule::class,
]
)
-class SysUITestModule {
- @Provides fun provideContext(test: SysuiTestCase): Context = test.context
+interface SysUITestModule {
- @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context
+ @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
+ @Binds fun bindContext(testableContext: TestableContext): Context
+ @Binds @Application fun bindAppContext(context: Context): Context
+ @Binds @Application fun bindAppResources(resources: Resources): Resources
+ @Binds @Main fun bindMainResources(resources: Resources): Resources
+ @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
- @Provides @Main
- fun provideResources(@Application context: Context): Resources = context.resources
+ companion object {
+ @Provides
+ fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
- @Provides
- fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher =
- test.fakeBroadcastDispatcher
+ @Provides
+ fun provideTestableResources(context: TestableContext): TestableResources =
+ context.getOrCreateTestableResources()
+
+ @Provides
+ fun provideResources(testableResources: TestableResources): Resources =
+ testableResources.resources
+
+ @Provides
+ fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher =
+ test.fakeBroadcastDispatcher
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index ff1d5b2..0cb913b 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -19,6 +19,7 @@
import android.app.admin.DevicePolicyManager
import android.os.UserManager
import android.util.DisplayMetrics
+import android.view.LayoutInflater
import com.android.internal.logging.MetricsLogger
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -37,12 +38,14 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -74,7 +77,11 @@
@get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
@get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@get:Provides val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(),
+ @get:Provides val layoutInflater: LayoutInflater = mock(),
+ @get:Provides
+ val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock(),
@get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
+ @get:Provides val notifCollection: NotifCollection = mock(),
@get:Provides val notificationListener: NotificationListener = mock(),
@get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
@get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index b31f630a4..e429446 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 5273e0e5..ac40ba6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -21,6 +21,7 @@
import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
@@ -35,8 +36,8 @@
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -50,12 +51,19 @@
import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.ClockTickRate;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -136,6 +144,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mFakeDateView.setTag(R.id.tag_smartspace_view, new Object());
mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object());
mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object());
@@ -176,12 +185,18 @@
mKeyguardSliceViewController,
mNotificationIconAreaController,
mSmartspaceController,
+ mock(ConfigurationController.class),
+ mock(ScreenOffAnimationController.class),
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
mDumpManager,
mClockEventController,
mLogBuffer,
+ mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
+ mock(ConfigurationState.class),
+ mock(DozeParameters.class),
+ mock(AlwaysOnDisplayNotificationIconViewStore.class),
KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
mFakeFeatureFlags
);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index aabdcb7..efb0887 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 @@
}
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 @@
@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 @@
}
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/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
deleted file mode 100644
index 89389b0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility;
-
-import android.os.RemoteException;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub {
-
- private final CountDownLatch mCountDownLatch;
- private final AtomicInteger mSuccessCount;
- private final AtomicInteger mFailedCount;
-
- MockMagnificationAnimationCallback(CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
- mSuccessCount = new AtomicInteger();
- mFailedCount = new AtomicInteger();
- }
-
- public int getSuccessCount() {
- return mSuccessCount.get();
- }
-
- public int getFailedCount() {
- return mFailedCount.get();
- }
-
- @Override
- public void onResult(boolean success) throws RemoteException {
- if (success) {
- mSuccessCount.getAndIncrement();
- } else {
- mFailedCount.getAndIncrement();
- }
- // It should be put at the last line to avoid making CountDownLatch#await passed without
- // updating values.
- mCountDownLatch.countDown();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 22ebd99..284c273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.animation.ValueAnimator;
@@ -33,7 +34,6 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.SurfaceControl;
import android.view.View;
@@ -46,13 +46,15 @@
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -61,22 +63,17 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@LargeTest
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private static final float DEFAULT_SCALE = 4.0f;
private static final float DEFAULT_CENTER_X = 400.0f;
private static final float DEFAULT_CENTER_Y = 500.0f;
- // The duration and period couldn't too short, otherwise the ValueAnimator and
- // Instrumentation.runOnMainSync won't work in expectation. (b/288926821)
- private static final long ANIMATION_DURATION_MS = 600;
- private static final long WAIT_FULL_ANIMATION_PERIOD = 1000;
- private static final long WAIT_INTERMEDIATE_ANIMATION_PERIOD = 250;
private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
@@ -105,10 +102,11 @@
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
private Instrumentation mInstrumentation;
- private long mWaitingAnimationPeriod;
- private long mWaitIntermediateAnimationPeriod;
+ private long mWaitAnimationDuration;
+ private long mWaitPartialAnimationDuration;
private TestableWindowManager mWindowManager;
+ private ValueAnimator mValueAnimator;
@Before
public void setUp() throws Exception {
@@ -118,10 +116,14 @@
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- mWaitingAnimationPeriod = WAIT_FULL_ANIMATION_PERIOD;
- mWaitIntermediateAnimationPeriod = WAIT_INTERMEDIATE_ANIMATION_PERIOD;
+ // Using the animation duration in WindowMagnificationAnimationController for testing.
+ mWaitAnimationDuration = mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_longAnimTime);
+ mWaitPartialAnimationDuration = mWaitAnimationDuration / 2;
+
+ mValueAnimator = newValueAnimator();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
- mContext, newValueAnimator());
+ mContext, mValueAnimator);
mController = new SpyWindowMagnificationController(mContext, mHandler,
mWindowMagnificationAnimationController,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
@@ -131,13 +133,15 @@
@After
public void tearDown() throws Exception {
- mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+ mInstrumentation.runOnMainSync(() -> {
+ mController.deleteWindowMagnification();
+ });
}
@Test
public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -161,16 +165,12 @@
@Test
public void enableWindowMagnificationWithoutCallback_enabled_expectedValues() {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@@ -178,14 +178,11 @@
@Test
public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback()
throws RemoteException {
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(1,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ enableWindowMagnificationAndWaitAnimating(
+ mWaitAnimationDuration, /* targetScale= */ 1.0f,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
- verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X,
+ verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
DEFAULT_CENTER_Y, 0f, 0f);
verify(mAnimationCallback).onResult(true);
}
@@ -193,23 +190,22 @@
@Test
public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ resetMockObjects();
mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback2);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
});
- SystemClock.sleep(mWaitingAnimationPeriod);
-
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -226,24 +222,17 @@
@Test
public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// The callback in 2nd enableWindowMagnification will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in 1st enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@@ -256,17 +245,16 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ Mockito.reset(mSpyController);
mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
});
- SystemClock.sleep(mWaitingAnimationPeriod);
-
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -293,17 +281,16 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ Mockito.reset(mSpyController);
mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
});
- SystemClock.sleep(mWaitingAnimationPeriod);
-
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -331,11 +318,8 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
assertEquals(WindowMagnificationAnimationController.STATE_DISABLED,
@@ -346,17 +330,15 @@
public void
enableMagnificationWithoutCallback_enabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE - 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
+
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
}
@@ -364,15 +346,12 @@
@Test
public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ enableWindowMagnificationAndWaitAnimating(
+ mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -383,28 +362,35 @@
@Test
public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
+ Mockito.reset(mSpyController);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ });
+
// Current spec shouldn't match given spec.
verify(mAnimationCallback2, never()).onResult(anyBoolean());
verify(mAnimationCallback).onResult(false);
- SystemClock.sleep(mWaitingAnimationPeriod);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ mInstrumentation.runOnMainSync(() -> {
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it
+ // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the
+ // animator directly to verify the result of animation is correct instead of querying
+ // the animation frame at a specific timing.
+ mValueAnimator.end();
+ });
+
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -424,18 +410,14 @@
enableMagnificationWithoutCallback_disabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
@@ -444,16 +426,13 @@
@Test
public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -470,17 +449,16 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ Mockito.reset(mSpyController);
mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback2);
mCurrentScale.set(mController.getScale());
mCurrentCenterX.set(mController.getCenterX());
mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
});
- SystemClock.sleep(mWaitingAnimationPeriod);
-
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -498,13 +476,17 @@
public void enableWindowMagnificationWithOffset_expectedValues() {
final float offsetRatio = -0.1f;
final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+
+ Mockito.reset(mSpyController);
mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
windowBounds.exactCenterX(), windowBounds.exactCenterY(),
offsetRatio, offsetRatio, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
});
- SystemClock.sleep(mWaitingAnimationPeriod);
+
+ // Wait for Rects update
+ waitForIdleSync();
final View attachedView = mWindowManager.getAttachedView();
assertNotNull(attachedView);
final Rect mirrorViewBound = new Rect();
@@ -516,111 +498,97 @@
(int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
(int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
-
}
@Test
- public void moveWindowMagnifierToPosition_enabled_expectedValues()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ public void moveWindowMagnifierToPosition_enabled_expectedValues() throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
+ targetCenterX, targetCenterY, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
});
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
- assertEquals(1, animationCallback.getSuccessCount());
- assertEquals(0, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback, never()).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(4);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
enableWindowMagnificationWithoutAnimation();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
});
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// only the last one callback will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// the others will return false
- assertEquals(3, animationCallback.getFailedCount());
+ verify(mAnimationCallback, times(3)).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
}
@Test
public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
+
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- Float.NaN, Float.NaN, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
+
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
+ enableWindowMagnificationWithoutAnimation();
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -632,7 +600,8 @@
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ resetMockObjects();
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -659,7 +628,7 @@
@Test
public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback()
throws RemoteException {
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
Mockito.verifyNoMoreInteractions(mSpyController);
verify(mAnimationCallback).onResult(true);
@@ -668,20 +637,25 @@
@Test
public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(
- mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ Mockito.reset(mSpyController);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.deleteWindowMagnification(
+ mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it
+ // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the
+ // animator directly to verify the result of animation is correct instead of querying
+ // the animation frame at a specific timing.
+ mValueAnimator.end();
+ });
+
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -702,14 +676,11 @@
@Test
public void deleteWindowMagnificationWithoutCallback_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(null);
- });
+ Mockito.reset(mSpyController);
+ deleteWindowMagnificationWithoutAnimation();
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
verify(mAnimationCallback).onResult(false);
@@ -717,13 +688,14 @@
@Test
public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2);
+ resetMockObjects();
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -738,8 +710,8 @@
@Test
public void deleteWindowMagnificationWithoutCallback_disabling_checkStartAndValues()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
deleteWindowMagnificationAndWaitAnimating(0, null);
@@ -757,9 +729,8 @@
final float offsetX = 50.0f;
final float offsetY =
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- + 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+ + 1.0f;
+ mInstrumentation.runOnMainSync(()-> mController.moveWindowMagnifier(offsetX, offsetY));
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY);
@@ -774,8 +745,8 @@
final float offsetY =
(float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+ mInstrumentation.runOnMainSync(() ->
+ mController.moveWindowMagnifier(offsetX, offsetY));
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y);
@@ -790,11 +761,10 @@
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE);
// while diagonal scrolling enabled,
// should move with both offsetX and offsetY without regrading offsetY/offsetX
- mInstrumentation.runOnMainSync(
- () -> {
- mController.setDiagonalScrolling(true);
- mController.moveWindowMagnifier(offsetX, offsetY);
- });
+ mInstrumentation.runOnMainSync(() -> {
+ mController.setDiagonalScrolling(true);
+ mController.moveWindowMagnifier(offsetX, offsetY);
+ });
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY);
@@ -806,14 +776,19 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
- mAnimationCallback));
- SystemClock.sleep(mWaitingAnimationPeriod);
+ mInstrumentation.runOnMainSync(() -> {
+ mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
+ private void advanceTimeBy(long timeDelta) {
+ mAnimatorTestRule.advanceTimeBy(timeDelta);
+ }
+
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -822,33 +797,49 @@
}
private void enableWindowMagnificationWithoutAnimation() {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
- });
+ enableWindowMagnificationWithoutAnimation(
+ DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+ }
+
+ private void enableWindowMagnificationWithoutAnimation(
+ float targetScale, float targetCenterX, float targetCenterY) {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(
+ targetScale, targetCenterX, targetCenterY, null);
+ });
}
private void enableWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
- });
- SystemClock.sleep(duration);
+ enableWindowMagnificationAndWaitAnimating(
+ duration, DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
+ }
+
+ private void enableWindowMagnificationAndWaitAnimating(
+ long duration,
+ float targetScale,
+ float targetCenterX,
+ float targetCenterY,
+ @Nullable IRemoteMagnificationAnimationCallback callback) {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(
+ targetScale, targetCenterX, targetCenterY, callback);
+ advanceTimeBy(duration);
+ });
+ }
+
+ private void deleteWindowMagnificationWithoutAnimation() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.deleteWindowMagnification(null);
+ });
}
private void deleteWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- resetMockObjects();
- mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
- });
- SystemClock.sleep(duration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
+ advanceTimeBy(duration);
+ });
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
@@ -937,9 +928,9 @@
}
}
- private static ValueAnimator newValueAnimator() {
+ private ValueAnimator newValueAnimator() {
final ValueAnimator valueAnimator = new ValueAnimator();
- valueAnimator.setDuration(ANIMATION_DURATION_MS);
+ valueAnimator.setDuration(mWaitAnimationDuration);
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
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 b8d2bdb..06421db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -45,6 +45,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -53,6 +54,7 @@
import android.animation.ValueAnimator;
import android.annotation.IdRes;
+import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -89,6 +91,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -102,6 +105,7 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -111,8 +115,6 @@
import org.mockito.MockitoAnnotations;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@LargeTest
@@ -120,12 +122,10 @@
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationControllerTest extends SysuiTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
- // The duration couldn't too short, otherwise the animation check on bounce effect
- // won't work in expectation. (b/299537784)
- private static final int BOUNCE_EFFECT_DURATION_MS = 2000;
- private static final long ANIMATION_DURATION_MS = 300;
- private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
@Mock
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
@@ -134,11 +134,16 @@
private WindowMagnifierCallback mWindowMagnifierCallback;
@Mock
IRemoteMagnificationAnimationCallback mAnimationCallback;
+ @Mock
+ IRemoteMagnificationAnimationCallback mAnimationCallback2;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@Mock
private SecureSettings mSecureSettings;
+ private long mWaitAnimationDuration;
+ private long mWaitBounceEffectDuration;
+
private Handler mHandler;
private TestableWindowManager mWindowManager;
private SysUiState mSysUiState;
@@ -194,6 +199,13 @@
mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
}
+ // Using the animation duration in WindowMagnificationAnimationController for testing.
+ mWaitAnimationDuration = mResources.getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+ // Using the bounce effect duration in WindowMagnificationController for testing.
+ mWaitBounceEffectDuration = mResources.getInteger(
+ com.android.internal.R.integer.config_shortAnimTime);
+
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, mValueAnimator);
mWindowMagnificationController =
@@ -208,7 +220,6 @@
mSysUiState,
() -> mWindowSessionSpy,
mSecureSettings);
- mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
@@ -281,9 +292,9 @@
/* magnificationFrameOffsetRatioY= */ 0,
Mockito.mock(IRemoteMagnificationAnimationCallback.class));
});
+ advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
- verify(mSfVsyncFrameProvider,
- timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any());
+ verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any());
}
@Test
@@ -401,14 +412,12 @@
@Test
public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
- throws InterruptedException {
+ throws RemoteException {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN, 0, 0, null);
});
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+
final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
@@ -417,12 +426,12 @@
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
+ targetCenterX, targetCenterY, mAnimationCallback);
});
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
- assertEquals(1, animationCallback.getSuccessCount());
- assertEquals(0, animationCallback.getFailedCount());
+ verify(mAnimationCallback, times(1)).onResult(eq(true));
+ verify(mAnimationCallback, never()).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
assertEquals(mWindowMagnificationController.getCenterX(),
@@ -435,14 +444,12 @@
@Test
public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
- throws InterruptedException {
+ throws RemoteException {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN, 0, 0, null);
});
- final CountDownLatch countDownLatch = new CountDownLatch(4);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+
final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
@@ -451,20 +458,20 @@
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
- centerX + 10, centerY + 10, animationCallback);
+ centerX + 10, centerY + 10, mAnimationCallback);
mWindowMagnificationController.moveWindowMagnifierToPosition(
- centerX + 20, centerY + 20, animationCallback);
+ centerX + 20, centerY + 20, mAnimationCallback);
mWindowMagnificationController.moveWindowMagnifierToPosition(
- centerX + 30, centerY + 30, animationCallback);
+ centerX + 30, centerY + 30, mAnimationCallback);
mWindowMagnificationController.moveWindowMagnifierToPosition(
- centerX + 40, centerY + 40, animationCallback);
+ centerX + 40, centerY + 40, mAnimationCallback2);
});
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// only the last one callback will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(eq(true));
// the others will return false
- assertEquals(3, animationCallback.getFailedCount());
+ verify(mAnimationCallback, times(3)).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
assertEquals(mWindowMagnificationController.getCenterX(),
@@ -1078,27 +1085,16 @@
final View mirrorView = mWindowManager.getAttachedView();
- final long timeout = SystemClock.uptimeMillis() + 5000;
final AtomicDouble maxScaleX = new AtomicDouble();
- final Runnable onAnimationFrame = new Runnable() {
- @Override
- public void run() {
- // For some reason the fancy way doesn't compile...
-// maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
- final double oldMax = maxScaleX.get();
- final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
- assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+ advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
+ // For some reason the fancy way doesn't compile...
+ // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
+ final double oldMax = maxScaleX.get();
+ final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
+ assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+ });
- if (SystemClock.uptimeMillis() < timeout) {
- mirrorView.postOnAnimation(this);
- }
- }
- };
- mirrorView.postOnAnimation(onAnimationFrame);
-
- waitForIdleSync();
-
- ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0);
+ assertTrue(maxScaleX.get() > 1.0);
}
@Test
@@ -1278,7 +1274,8 @@
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;
@@ -1454,4 +1451,23 @@
return newRotation;
}
+ // advance time based on the device frame refresh rate
+ private void advanceTimeBy(long timeDelta) {
+ advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
+ }
+
+ // advance time based on the device frame refresh rate, and trigger runnable on each refresh
+ private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
+ final float frameRate = mContext.getDisplay().getRefreshRate();
+ final int timeSlot = (int) (1000 / frameRate);
+ int round = (int) Math.ceil((double) timeDelta / timeSlot);
+ for (; round >= 0; round--) {
+ mInstrumentation.runOnMainSync(() -> {
+ mAnimatorTestRule.advanceTimeBy(timeSlot);
+ if (runnableOnEachRefresh != null) {
+ runnableOnEachRefresh.run();
+ }
+ });
+ }
+ }
}
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 ec6ec63..e832940 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 @@
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 aed795a..76094c1 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.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.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 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
@@ -116,7 +126,8 @@
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,7 +232,8 @@
}
@Test
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
+ @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();
@@ -233,15 +245,49 @@
}
@Test
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
- final float menuTop = IME_TOP + 200;
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+ @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(0);
- assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+ 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() {
+ 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(beforePosition.x);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
}
private void setupEnabledAccessibilityServiceList() {
@@ -294,4 +340,21 @@
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 2975549..c7bb0f5 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 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 f6b284f..d6aa9ac 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 @@
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 @@
// 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/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
new file mode 100644
index 0000000..034b802
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.common.ui
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConfigurationStateTest : SysuiTestCase() {
+
+ private val configurationController: ConfigurationController = mock()
+ private val layoutInflater = TestLayoutInflater()
+
+ val underTest = ConfigurationState(configurationController, context, layoutInflater)
+
+ @Test
+ fun reinflateAndBindLatest_inflatesWithoutEmission() = runTest {
+ var callbackCount = 0
+ backgroundScope.launch {
+ underTest.reinflateAndBindLatest<View>(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ ) {
+ callbackCount++
+ null
+ }
+ }
+
+ // Inflates without an emission
+ runCurrent()
+ assertThat(layoutInflater.inflationCount).isEqualTo(1)
+ assertThat(callbackCount).isEqualTo(1)
+ }
+
+ @Test
+ fun reinflateAndBindLatest_reinflatesOnThemeChanged() = runTest {
+ var callbackCount = 0
+ backgroundScope.launch {
+ underTest.reinflateAndBindLatest<View>(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ ) {
+ callbackCount++
+ null
+ }
+ }
+ runCurrent()
+
+ val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+ verify(configurationController, atLeastOnce()).addCallback(capture())
+ }
+
+ listOf(1, 2, 3).forEach { count ->
+ assertThat(layoutInflater.inflationCount).isEqualTo(count)
+ assertThat(callbackCount).isEqualTo(count)
+ configListeners.forEach { it.onThemeChanged() }
+ runCurrent()
+ }
+ }
+
+ @Test
+ fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() = runTest {
+ var callbackCount = 0
+ backgroundScope.launch {
+ underTest.reinflateAndBindLatest<View>(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ ) {
+ callbackCount++
+ null
+ }
+ }
+ runCurrent()
+
+ val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
+ verify(configurationController, atLeastOnce()).addCallback(capture())
+ }
+
+ listOf(1, 2, 3).forEach { count ->
+ assertThat(layoutInflater.inflationCount).isEqualTo(count)
+ assertThat(callbackCount).isEqualTo(count)
+ configListeners.forEach { it.onDensityOrFontScaleChanged() }
+ runCurrent()
+ }
+ }
+
+ @Test
+ fun testReinflateAndBindLatest_disposesOnCancel() = runTest {
+ var callbackCount = 0
+ var disposed = false
+ val job = launch {
+ underTest.reinflateAndBindLatest<View>(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ ) {
+ callbackCount++
+ DisposableHandle { disposed = true }
+ }
+ }
+
+ runCurrent()
+ job.cancelAndJoin()
+ assertThat(disposed).isTrue()
+ }
+
+ inner class TestLayoutInflater : LayoutInflater(context) {
+
+ var inflationCount = 0
+
+ override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
+ inflationCount++
+ return View(context)
+ }
+
+ override fun cloneInContext(p0: Context?): LayoutInflater {
+ // not needed for this test
+ return this
+ }
+ }
+}
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 1b2fc93d..2f4fc96 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 @@
isAttachedToWindow = { isAttachedToWindow },
onLongPressDetected = onLongPressDetected,
onSingleTapDetected = onSingleTapDetected,
+ longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }
)
underTest.isLongPressHandlingEnabled = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
index 0e14591..52c6e22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -18,9 +18,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -61,80 +63,70 @@
@Test
fun restart_ImmediatelySatisfied() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = true
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(true)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForConditionA() =
testScope.runTest {
- conditionA.canRestart = false
- conditionB.canRestart = true
+ conditionA.canRestart.emit(false)
+ conditionB.canRestart.emit(true)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionA.canRestart = true
- conditionA.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForConditionB() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = false
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(false)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionB.canRestart = true
- conditionB.retryFn?.invoke()
- advanceUntilIdle()
+ conditionB.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForAllConditions() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = false
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(false)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
// B becomes true, but A is now false
- conditionA.canRestart = false
- conditionB.canRestart = true
- conditionB.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(false)
+ conditionB.canRestart.emit(true)
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionA.canRestart = true
- conditionA.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
class FakeCondition : ConditionalRestarter.Condition {
- var retryFn: (() -> Unit)? = null
- var canRestart = false
+ val canRestart = MutableStateFlow(false)
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- this.retryFn = retryFn
-
- return canRestart
- }
+ override val canRestartNow: Flow<Boolean> = canRestart
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
new file mode 100644
index 0000000..db6f85f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class NotOccludedConditionTest : SysuiTestCase() {
+ private lateinit var condition: NotOccludedCondition
+
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private val transitionValue = MutableStateFlow(0f)
+
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(keyguardTransitionInteractor.transitionValue(KeyguardState.OCCLUDED))
+ .thenReturn(transitionValue)
+ condition = NotOccludedCondition({ keyguardTransitionInteractor })
+ testScope.runCurrent()
+ }
+
+ @Test
+ fun testCondition_occluded() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(1f)
+ assertThat(canRestart).isFalse()
+ }
+
+ @Test
+ fun testCondition_notOccluded() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(0f)
+ assertThat(canRestart).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(1f)
+
+ assertThat(canRestart).isFalse()
+
+ transitionValue.emit(0f)
+
+ assertThat(canRestart).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 647b05a..7d7abab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -17,8 +17,13 @@
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.BatteryController
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -35,42 +40,51 @@
private lateinit var condition: PluggedInCondition
@Mock private lateinit var batteryController: BatteryController
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
+ private val callbackCaptor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- condition = PluggedInCondition(batteryController)
+
+ condition = PluggedInCondition({ batteryController })
}
@Test
- fun testCondition_unplugged() {
- whenever(batteryController.isPluggedIn).thenReturn(false)
+ fun testCondition_unplugged() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
- assertThat(condition.canRestartNow({})).isFalse()
- }
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ assertThat(canRestart).isFalse()
+ }
@Test
- fun testCondition_pluggedIn() {
- whenever(batteryController.isPluggedIn).thenReturn(true)
+ fun testCondition_pluggedIn() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(true)
- assertThat(condition.canRestartNow({})).isTrue()
- }
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ assertThat(canRestart).isTrue()
+ }
@Test
- fun testCondition_invokesRetry() {
- whenever(batteryController.isPluggedIn).thenReturn(false)
- var retried = false
- val retryFn = { retried = true }
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
- // No restart yet, but we do register a listener now.
- assertThat(condition.canRestartNow(retryFn)).isFalse()
- val captor =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
- verify(batteryController).addCallback(captor.capture())
+ val canRestart by collectLastValue(condition.canRestartNow)
- whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(canRestart).isFalse()
- captor.value.onBatteryLevelChanged(0, true, true)
- assertThat(retried).isTrue()
- }
+ verify(batteryController).addCallback(callbackCaptor.capture())
+
+ callbackCaptor.value.onBatteryLevelChanged(0, true, false)
+
+ assertThat(canRestart).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index f7a773e..1f04828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -17,15 +17,17 @@
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -36,42 +38,50 @@
class ScreenIdleConditionTest : SysuiTestCase() {
private lateinit var condition: ScreenIdleCondition
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var powerInteractor: PowerInteractor
+ private val isAsleep = MutableStateFlow(false)
+
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- condition = ScreenIdleCondition(wakefulnessLifecycle)
+ whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
+ condition = ScreenIdleCondition({ powerInteractor })
}
@Test
- fun testCondition_awake() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ fun testCondition_awake() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- assertThat(condition.canRestartNow {}).isFalse()
- }
+ isAsleep.emit(false)
+
+ assertThat(canRestart).isFalse()
+ }
@Test
- fun testCondition_asleep() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ fun testCondition_asleep() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- assertThat(condition.canRestartNow {}).isTrue()
- }
+ isAsleep.emit(true)
+
+ assertThat(canRestart).isTrue()
+ }
@Test
- fun testCondition_invokesRetry() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- var retried = false
- val retryFn = { retried = true }
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- // No restart yet, but we do register a listener now.
- assertThat(condition.canRestartNow(retryFn)).isFalse()
- val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
- verify(wakefulnessLifecycle).addObserver(captor.capture())
+ isAsleep.emit(false)
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ assertThat(canRestart).isFalse()
- captor.value.onFinishedGoingToSleep()
- assertThat(retried).isTrue()
- }
+ isAsleep.emit(true)
+
+ assertThat(canRestart).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
index bb6786a..f4d2cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -18,6 +18,17 @@
import android.platform.test.flag.junit.SetFlagsRule
+/**
+ * Set the given flag's value to the real value for the current build configuration. This prevents
+ * test code from crashing because it is reading an unspecified flag value.
+ *
+ * REMINDER: You should always test your code with your flag in both configurations, so generally
+ * you should be explicitly enabling or disabling your flag. This method is for situations where the
+ * flag needs to be read (e.g. in the class constructor), but its value shouldn't affect the actual
+ * test cases. In those cases, it's mildly safer to use this method than to hard-code `false` or
+ * `true` because then at least if you're wrong, and the flag value *does* matter, you'll notice
+ * when the flag is flipped and tests start failing.
+ */
fun SetFlagsRule.setFlagDefault(flagName: String) {
if (getFlagDefault(flagName)) {
enableFlags(flagName)
@@ -26,6 +37,19 @@
}
}
+/**
+ * Set the given flag to an explicit value, or, if null, to the real value for the current build
+ * configuration. This allows for convenient provisioning in tests where certain tests don't care
+ * what the value is (`setFlagValue(FLAG_FOO, null)`), and others want an explicit value.
+ */
+fun SetFlagsRule.setFlagValue(name: String, value: Boolean?) {
+ when (value) {
+ null -> setFlagDefault(name)
+ true -> enableFlags(name)
+ false -> disableFlags(name)
+ }
+}
+
// NOTE: This code uses reflection to gain access to private members of aconfig generated
// classes (in the same way SetFlagsRule does internally) because this is the only way to get
// at the underlying information and read the current value of the flag.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 819d08a..9bb2434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -43,7 +43,6 @@
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -82,6 +81,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
@@ -94,6 +94,8 @@
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
@@ -116,8 +118,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -195,9 +195,11 @@
}
powerRepository = FakePowerRepository()
- powerInteractor = PowerInteractorFactory.create(
- repository = powerRepository,
- ).powerInteractor
+ powerInteractor =
+ PowerInteractorFactory.create(
+ repository = powerRepository,
+ )
+ .powerInteractor
val withDeps =
KeyguardInteractorFactory.create(
@@ -210,10 +212,10 @@
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- keyguardInteractor = keyguardInteractor,
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = keyguardTransitionRepository,
+ keyguardInteractor = keyguardInteractor,
)
.keyguardTransitionInteractor
@@ -635,11 +637,7 @@
@Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
- testScope.runTest {
- testGatingCheckForFaceAuth {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.setAsleepForTest() } }
@Test
fun authenticateDoesNotRunWhenSecureCameraIsActive() =
@@ -736,17 +734,21 @@
allPreconditionsToRunFaceAuthAreTrue()
Log.i("TEST", "started waking")
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OFF,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.STARTED,
- ))
+ )
+ )
runCurrent()
Log.i("TEST", "sending display off")
@@ -766,11 +768,13 @@
testScope.runTest {
testGatingCheckForFaceAuth {
powerInteractor.onFinishedWakingUp()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
displayRepository.emit(
@@ -919,11 +923,7 @@
@Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
- testScope.runTest {
- testGatingCheckForDetect {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForDetect { powerInteractor.setAsleepForTest() } }
@Test
fun detectDoesNotRunWhenSecureCameraIsActive() =
@@ -1114,6 +1114,32 @@
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
faceAuthenticateIsCalled()
}
+ @Test
+ fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+ runCurrent()
+ assertThat(canFaceAuthRun()).isTrue()
+
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+ runCurrent()
+
+ faceAuthenticateIsCalled()
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_LOCKOUT_PERMANENT,
+ "Too many attempts, face not available"
+ )
+
+ val lockoutError = authStatus() as ErrorFaceAuthenticationStatus
+ assertThat(lockedOut()).isTrue()
+ assertThat(lockoutError.isLockoutError()).isTrue()
+
+ authenticationCallback.value.onAuthenticationFailed()
+ runCurrent()
+
+ assertThat(authStatus()).isEqualTo(lockoutError)
+ }
private suspend fun TestScope.testGatingCheckForFaceAuth(
gatingCheckModifier: suspend () -> Unit
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 ef03fdf..6c4bb37 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 @@
}
@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 b32905f..27325d3 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 @@
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 a5d7457..6cd7406 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.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.verify
import org.mockito.junit.MockitoJUnit
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardKeyEventInteractorTest : SysuiTestCase() {
@@ -128,58 +130,62 @@
}
@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 @@
.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 50ee026..8cfa87d 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.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 @@
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 @@
underTest =
DefaultKeyguardBlueprint(
defaultIndicationAreaSection,
- defaultLockIconSection,
+ mDefaultDeviceEntryIconSection,
defaultShortcutsSection,
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
@@ -101,14 +101,14 @@
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
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
index 7495637..5f22c7d 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.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.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 @@
@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 @@
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 deefab6..b101acf 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 @@
context,
controllerFactory,
lmmFactory,
- mr2,
+ { mr2 },
muteAwaitFactory,
configurationController,
- localBluetoothManager,
+ { localBluetoothManager },
fakeFgExecutor,
fakeBgExecutor,
dumpster,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
index 429aad1..da448aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -63,4 +63,31 @@
verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN)
}
+
+ @Test
+ fun notifyPermissionRequestDisplayed_forwardsToService() {
+ val hostUid = 987
+
+ logger.notifyPermissionRequestDisplayed(hostUid)
+
+ verify(service).notifyPermissionRequestDisplayed(hostUid)
+ }
+
+ @Test
+ fun notifyProjectionCancelled_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+
+ logger.notifyProjectionRequestCancelled(hostUid)
+
+ verify(service).notifyPermissionRequestCancelled(hostUid)
+ }
+
+ @Test
+ fun notifyAppSelectorDisplayed_forwardsToService() {
+ val hostUid = 654
+
+ logger.notifyAppSelectorDisplayed(hostUid)
+
+ verify(service).notifyAppSelectorDisplayed(hostUid)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 6cdf4ef..44798ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -4,7 +4,6 @@
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
@@ -44,7 +43,7 @@
private val thumbnailLoader = FakeThumbnailLoader()
- private fun createController(isFirstStart: Boolean = true) =
+ private fun createController(isFirstStart: Boolean = true, hostUid: Int = 123) =
MediaProjectionAppSelectorController(
taskListProvider,
view,
@@ -55,7 +54,8 @@
callerPackageName,
thumbnailLoader,
isFirstStart,
- logger
+ logger,
+ hostUid,
)
@Before
@@ -212,20 +212,31 @@
@Test
fun init_firstStart_logsAppSelectorDisplayed() {
- val controller = createController(isFirstStart = true)
+ val hostUid = 123456789
+ val controller = createController(isFirstStart = true, hostUid)
controller.init()
- verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ verify(logger).notifyAppSelectorDisplayed(hostUid)
}
@Test
fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
- val controller = createController(isFirstStart = false)
+ val hostUid = 123456789
+ val controller = createController(isFirstStart = false, hostUid)
controller.init()
- verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ verify(logger, never()).notifyAppSelectorDisplayed(hostUid)
+ }
+
+ @Test
+ fun onSelectorDismissed_logsProjectionRequestCancelled() {
+ val hostUid = 123
+
+ createController(hostUid = hostUid).onSelectorDismissed()
+
+ verify(logger).notifyProjectionRequestCancelled(hostUid)
}
private fun givenCaptureAllowed(isAllow: Boolean) {
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 43cf1b5..ae47a7b 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.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.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 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -357,8 +356,7 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -373,7 +371,7 @@
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 0e6e4fa..c711806 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 @@
}
@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/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index ac03073..c6d156f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -96,6 +97,8 @@
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@Mock
private Dialog mPermissionDialogPrompt;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -106,6 +109,7 @@
mTestableLooper = TestableLooper.get(this);
+ when(mUserContextProvider.getUserContext()).thenReturn(mContext);
when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(
@@ -124,7 +128,8 @@
mKeyguardStateController,
mDialogLaunchAnimator,
mPanelInteractor,
- mMediaProjectionMetricsLogger
+ mMediaProjectionMetricsLogger,
+ mUserContextProvider
);
mTile.initialize();
@@ -308,7 +313,8 @@
onDismissAction.getValue().onDismiss();
verify(mPermissionDialogPrompt).show();
- verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed();
+ verify(mMediaProjectionMetricsLogger)
+ .notifyPermissionRequestDisplayed(mContext.getUserId());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index 5659f01..95ee3b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -32,16 +32,16 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
@Mock private lateinit var activityStarted: ActivityStarter
- lateinit var underTest: QSTileIntentUserActionHandler
+ lateinit var underTest: QSTileIntentUserInputHandler
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = QSTileIntentUserActionHandler(activityStarted)
+ underTest = QSTileIntentUserInputHandler(activityStarted)
}
@Test
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 a6199c2..2bdc154 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 @@
@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 @@
}
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/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index 9907278..31d02ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.util.mockito.any
@@ -144,7 +143,6 @@
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- StateUpdateTrigger.ForceUpdate,
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -152,21 +150,36 @@
assertThat(logBuffer.getStringBuffer())
.contains(
"tile state update: " +
- "trigger=force, " +
- "state=[" +
- "label=, " +
+ "state=[label=, " +
"state=INACTIVE, " +
"s_label=null, " +
"cd=null, " +
"sd=null, " +
"svi=None, " +
"enabled=ENABLED, " +
- "a11y=null" +
- "], " +
+ "a11y=null], " +
"data=test_data"
)
}
+ @Test
+ fun testLogForceUpdate() {
+ underTest.logForceUpdate(
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
+ }
+
+ @Test
+ fun testLogInitialUpdate() {
+ underTest.logInitialRequest(
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
+ }
+
private fun LogBuffer.getStringBuffer(): String {
val stringWriter = StringWriter()
dump(PrintWriter(stringWriter), 0)
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 fc2b7a64..479e62d 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 @@
@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 @@
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 8b66040..3b6bfee 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.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 @@
@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 @@
ENABLED,
subtitleResId,
bluetoothTileDialogCallback,
+ fakeSystemClock,
uiEventLogger,
+ logger,
mContext
)
icon = Pair(drawable, DEVICE_NAME)
@@ -109,7 +116,9 @@
ENABLED,
subtitleResId,
bluetoothTileDialogCallback,
+ fakeSystemClock,
uiEventLogger,
+ logger,
mContext
)
bluetoothTileDialog.show()
@@ -138,7 +147,9 @@
ENABLED,
subtitleResId,
bluetoothTileDialogCallback,
+ fakeSystemClock,
uiEventLogger,
+ logger,
mContext
)
.Adapter(bluetoothTileDialogCallback)
@@ -162,7 +173,9 @@
ENABLED,
subtitleResId,
bluetoothTileDialogCallback,
+ fakeSystemClock,
uiEventLogger,
+ logger,
mContext
)
.Adapter(bluetoothTileDialogCallback)
@@ -182,7 +195,9 @@
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 dcda005..fb5dd21 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 @@
@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 @@
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 428f79c..4c173cc 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.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.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 @@
@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 @@
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 @@
)
val latest by collectLastValue(interactor.deviceItemUpdate)
- interactor.updateDeviceItems(mContext)
+ interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
assertThat(latest).isEqualTo(emptyList<DeviceItem>())
}
@@ -124,7 +133,7 @@
)
val latest by collectLastValue(interactor.deviceItemUpdate)
- interactor.updateDeviceItems(mContext)
+ interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
assertThat(latest).isEqualTo(emptyList<DeviceItem>())
}
@@ -139,7 +148,7 @@
)
val latest by collectLastValue(interactor.deviceItemUpdate)
- interactor.updateDeviceItems(mContext)
+ interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
assertThat(latest).isEqualTo(listOf(deviceItem1))
}
@@ -154,7 +163,7 @@
)
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 @@
`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 @@
`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/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
new file mode 100644
index 0000000..682b2d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QSTileConfigProviderTest : SysuiTestCase() {
+
+ private val underTest =
+ QSTileConfigProviderImpl(
+ mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC })
+ )
+
+ @Test
+ fun providerReturnsConfig() {
+ assertThat(underTest.getConfig(VALID_SPEC.spec)).isNotNull()
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throwsForInvalidSpec() {
+ underTest.getConfig(INVALID_SPEC.spec)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun validatesSpecUponCreation() {
+ QSTileConfigProviderImpl(
+ mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC })
+ )
+ }
+
+ private companion object {
+
+ val VALID_SPEC = TileSpec.create("valid_tile_spec")
+ val INVALID_SPEC = TileSpec.create("invalid_tile_spec")
+ }
+}
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 2084aeb..9bf4a75 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,24 +16,23 @@
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
-import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
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.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
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
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -55,6 +54,7 @@
@Mock private lateinit var qsTileLogger: QSTileLogger
@Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+ private val fakeUserRepository = FakeUserRepository()
private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
@@ -76,9 +76,6 @@
testScope.runTest {
assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
- underTest.onLifecycle(QSTileLifecycle.ALIVE)
- underTest.onUserIdChanged(1)
-
assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
underTest.state.launchIn(backgroundScope)
@@ -86,37 +83,35 @@
assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
assertThat(fakeQSTileDataInteractor.dataRequests.first())
- .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+ .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0)))
}
private fun createViewModel(
scope: TestScope,
config: QSTileConfig = TEST_QS_TILE_CONFIG,
): QSTileViewModel =
- BaseQSTileViewModel(
+ QSTileViewModelImpl(
config,
- fakeQSTileUserActionInteractor,
- fakeQSTileDataInteractor,
- object : QSTileDataToStateMapper<Any> {
- override fun map(config: QSTileConfig, data: Any): QSTileState =
- QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+ { fakeQSTileUserActionInteractor },
+ { fakeQSTileDataInteractor },
+ {
+ object : QSTileDataToStateMapper<Any> {
+ override fun map(config: QSTileConfig, data: Any): QSTileState =
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+ }
},
fakeDisabledByPolicyInteractor,
+ fakeUserRepository,
fakeFalsingManager,
qsTileAnalytics,
qsTileLogger,
+ FakeSystemClock(),
testCoroutineDispatcher,
scope.backgroundScope,
)
private companion object {
- val TEST_QS_TILE_CONFIG =
- QSTileConfig(
- TileSpec.create("default"),
- Icon.Resource(0, null),
- 0,
- InstanceId.fakeInstanceId(0),
- )
+ val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {}
}
}
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 e1345d2..c1f2d0c 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.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.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 @@
powerInteractor = powerInteractor,
)
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
+ }
+
@Test
fun hydrateVisibility() =
testScope.runTest {
@@ -520,7 +525,6 @@
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 f6195aa..0bed4d0 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.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 @@
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/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index c439cfe..49049bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenrecord;
+import static android.os.Process.myUid;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -31,8 +33,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
@@ -60,6 +62,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
/**
* Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
* Notification.
@@ -117,10 +120,6 @@
// starting, and notifies listeners.
@Test
public void testCancelCountdown() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
mController.startCountdown(100, 10, null, null);
assertTrue(mController.isStarting());
@@ -137,10 +136,6 @@
// Test that when recording is started, the start intent is sent and listeners are notified.
@Test
public void testStartRecording() throws PendingIntent.CanceledException {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, null);
@@ -151,10 +146,6 @@
// Test that when recording is stopped, the stop intent is sent and listeners are notified.
@Test
public void testStopRecording() throws PendingIntent.CanceledException {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
@@ -182,10 +173,6 @@
// Test that broadcast will update state
@Test
public void testUpdateStateBroadcast() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
// When a recording has started
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, null);
@@ -211,10 +198,6 @@
// Test that switching users will stop an ongoing recording
@Test
public void testUserChange() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
// If we are recording
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
@@ -231,10 +214,6 @@
@Test
public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -247,10 +226,6 @@
@Test
public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
@@ -262,10 +237,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -278,10 +249,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -294,9 +261,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -306,7 +270,7 @@
verify(mMediaProjectionMetricsLogger)
.notifyProjectionInitiated(
- TEST_USER_ID,
+ /* hostUid= */ myUid(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index da4dc85..fd38139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord
+import android.content.Intent
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -25,11 +26,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
import com.android.systemui.mediaprojection.permission.SINGLE_APP
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
@@ -38,6 +42,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -51,6 +57,7 @@
@Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var flags: FeatureFlags
@Mock private lateinit var onStartRecordingClicked: Runnable
+ @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
private lateinit var dialog: ScreenRecordPermissionDialog
@@ -62,10 +69,12 @@
ScreenRecordPermissionDialog(
context,
UserHandle.of(0),
+ TEST_HOST_UID,
controller,
starter,
userContextProvider,
- onStartRecordingClicked
+ onStartRecordingClicked,
+ mediaProjectionMetricsLogger,
)
dialog.onCreate(null)
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
@@ -105,6 +114,19 @@
}
@Test
+ fun startClicked_singleAppSelected_passesHostUidToAppSelector() {
+ dialog.show()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ clickOnStart()
+
+ assertExtraPassedToAppSelector(
+ extraKey = MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+ value = TEST_HOST_UID
+ )
+ }
+
+ @Test
fun showDialog_dialogIsShowing() {
dialog.show()
@@ -129,13 +151,51 @@
assertThat(dialog.isShowing).isFalse()
}
+ @Test
+ fun showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
+ dialog.show()
+
+ clickOnCancel()
+ clickOnCancel()
+
+ verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
+ }
+
+ @Test
+ fun dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
+ dialog.show()
+
+ TestableLooper.get(this).runWithLooper {
+ dialog.dismiss()
+ dialog.dismiss()
+ }
+
+ verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
+ }
+
private fun clickOnCancel() {
dialog.requireViewById<View>(android.R.id.button2).performClick()
}
+ private fun clickOnStart() {
+ dialog.requireViewById<View>(android.R.id.button1).performClick()
+ }
+
private fun onSpinnerItemSelected(position: Int) {
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
checkNotNull(spinner.onItemSelectedListener)
.onItemSelected(spinner, mock(), position, /* id= */ 0)
}
+
+ private fun assertExtraPassedToAppSelector(extraKey: String, value: Int) {
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(starter).startActivity(intentCaptor.capture(), /* dismissShade= */ eq(true))
+
+ val intent = intentCaptor.value
+ assertThat(intent.extras!!.getInt(extraKey)).isEqualTo(value)
+ }
+
+ companion object {
+ private const val TEST_HOST_UID = 12345
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 6223e25..2ce4b04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -84,6 +84,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.view.LongPressHandlingView;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
@@ -120,6 +121,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -137,6 +140,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -168,6 +172,7 @@
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -176,8 +181,10 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
@@ -197,6 +204,8 @@
import java.util.Optional;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.StateFlowKt;
+import kotlinx.coroutines.test.TestScope;
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@@ -324,7 +333,6 @@
mEmptySpaceClickListenerCaptor;
@Mock protected ActivityStarter mActivityStarter;
@Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
- @Mock private ShadeInteractor mShadeInteractor;
@Mock private JavaAdapter mJavaAdapter;
@Mock private CastController mCastController;
@Mock private KeyguardRootView mKeyguardRootView;
@@ -335,6 +343,9 @@
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected KeyguardInteractor mKeyguardInteractor;
+ protected SceneTestUtils mUtils = new SceneTestUtils(this);
+ protected TestScope mTestScope = mUtils.getTestScope();
+ protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
@@ -370,10 +381,31 @@
mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
mShadeRepository = new FakeShadeRepository();
mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
+ when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
+ StateFlowKt.MutableStateFlow(false));
+ mShadeInteractor = new ShadeInteractor(
+ mTestScope.getBackgroundScope(),
+ new FakeDeviceProvisioningRepository(),
+ new FakeDisableFlagsRepository(),
+ mDozeParameters,
+ new FakeSceneContainerFlags(),
+ mUtils::sceneInteractor,
+ mFakeKeyguardRepository,
+ mKeyguardTransitionInteractor,
+ mPowerInteractor,
+ new FakeUserSetupRepository(),
+ mock(UserSwitcherInteractor.class),
+ new SharedNotificationContainerInteractor(
+ new FakeConfigurationRepository(),
+ mContext,
+ new ResourcesSplitShadeStateController()
+ ),
+ mShadeRepository
+ );
SystemClock systemClock = new FakeSystemClock();
- mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
- mInteractionJankMonitor, mShadeExpansionStateManager);
+ mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
+ mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor);
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -532,8 +564,9 @@
new NotificationWakeUpCoordinator(
mDumpManager,
mock(HeadsUpManager.class),
- new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
- mInteractionJankMonitor, mShadeExpansionStateManager),
+ new StatusBarStateControllerImpl(new UiEventLoggerFake(),
+ mInteractionJankMonitor,
+ mJavaAdapter, () -> mShadeInteractor),
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index be82bc3..4ba850c 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.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 @@
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 b421e1b..4e3e165 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.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 @@
@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 @@
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 9c57101..3d5d26a 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.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 @@
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 0fcfaf9..2f45b12 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.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;
@@ -188,8 +187,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
- mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
- mInteractionJankMonitor, mShadeExpansionStateManager);
+ mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
+ mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor);
FakeDeviceProvisioningRepository deviceProvisioningRepository =
new FakeDeviceProvisioningRepository();
@@ -219,7 +218,6 @@
powerInteractor,
featureFlags,
sceneContainerFlags,
- new FakeDeviceEntryRepository(),
new FakeKeyguardBouncerRepository(),
configurationRepository,
mShadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 8f06e63..6eabf44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -145,22 +145,15 @@
}
@Test
- public void testOnRepeatedlyLoadUnload_PluginFreed() {
+ public void testOnUnloadAfterLoad() {
mPluginInstance.onCreate();
mPluginInstance.loadPlugin();
+ assertNotNull(mPluginInstance.getPlugin());
assertInstances(1, 1);
mPluginInstance.unloadPlugin();
assertNull(mPluginInstance.getPlugin());
assertInstances(0, 0);
-
- mPluginInstance.loadPlugin();
- assertInstances(1, 1);
-
- mPluginInstance.unloadPlugin();
- mPluginInstance.onDestroy();
- assertNull(mPluginInstance.getPlugin());
- assertInstances(0, 0);
}
@Test
@@ -169,7 +162,7 @@
mPluginInstance.onCreate();
assertEquals(1, mPluginListener.mAttachedCount);
assertEquals(0, mPluginListener.mLoadCount);
- assertEquals(null, mPluginInstance.getPlugin());
+ assertNull(mPluginInstance.getPlugin());
assertInstances(0, 0);
}
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 2bcad1d..dd3ac92 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 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 @@
private void setupFingerprintUnlockPossible(boolean possible) {
when(mKeyguardUpdateMonitor
- .getCachedIsUnlockWithFingerprintPossible(getCurrentUser()))
+ .isUnlockWithFingerprintPossible(getCurrentUser()))
.thenReturn(possible);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index e8923a5..970a0f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -9,13 +9,18 @@
import com.android.TestMocksModule
import com.android.systemui.ExpandHelper
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -26,7 +31,9 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
import dagger.BindsInstance
import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,6 +58,7 @@
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -64,10 +72,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
+ private lateinit var transitionController: LockscreenShadeTransitionController
private lateinit var testComponent: TestComponent
-
- private val transitionController
- get() = testComponent.transitionController
private val configurationController
get() = testComponent.configurationController
private val disableFlagsRepository
@@ -85,8 +91,11 @@
@Mock lateinit var mediaHierarchyManager: MediaHierarchyManager
@Mock lateinit var nsslController: NotificationStackScrollLayoutController
@Mock lateinit var qS: QS
+ @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
@Mock lateinit var scrimController: ScrimController
@Mock lateinit var shadeViewController: ShadeViewController
+ @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
+ @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
@Mock lateinit var stackscroller: NotificationStackScrollLayout
@Mock lateinit var statusbarStateController: SysuiStatusBarStateController
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
@@ -135,6 +144,49 @@
)
)
+ transitionController =
+ LockscreenShadeTransitionController(
+ statusBarStateController = statusbarStateController,
+ logger = mock(),
+ keyguardBypassController = keyguardBypassController,
+ lockScreenUserManager = lockScreenUserManager,
+ falsingCollector = FalsingCollectorFake(),
+ ambientState = mock(),
+ mediaHierarchyManager = mediaHierarchyManager,
+ scrimTransitionController =
+ LockscreenShadeScrimTransitionController(
+ scrimController = scrimController,
+ context = context,
+ configurationController = configurationController,
+ dumpManager = mock(),
+ splitShadeStateController = ResourcesSplitShadeStateController()
+ ),
+ keyguardTransitionControllerFactory = { notificationPanelController ->
+ LockscreenShadeKeyguardTransitionController(
+ mediaHierarchyManager = mediaHierarchyManager,
+ notificationPanelController = notificationPanelController,
+ context = context,
+ configurationController = configurationController,
+ dumpManager = mock(),
+ splitShadeStateController = ResourcesSplitShadeStateController()
+ )
+ },
+ depthController = depthController,
+ context = context,
+ splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
+ singleShadeOverScrollerFactory = { singleShadeOverScroller },
+ activityStarter = mock(),
+ wakefulnessLifecycle = mock(),
+ configurationController = configurationController,
+ falsingManager = FalsingManagerFake(),
+ dumpManager = mock(),
+ qsTransitionControllerFactory = { qsTransitionController },
+ shadeRepository = testComponent.shadeRepository,
+ shadeInteractor = testComponent.shadeInteractor,
+ powerInteractor = testComponent.powerInteractor,
+ splitShadeStateController = ResourcesSplitShadeStateController(),
+ )
+
transitionController.addCallback(transitionControllerCallback)
transitionController.shadeViewController = shadeViewController
transitionController.centralSurfaces = centralSurfaces
@@ -259,7 +311,7 @@
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
verify(transitionControllerCallback, never())
.setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qS, never()).setTransitionToFullShadeProgress(anyBoolean(), anyFloat(), anyFloat())
+ verify(qsTransitionController, never()).dragDownAmount = anyFloat()
}
@Test
@@ -270,7 +322,7 @@
verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
verify(transitionControllerCallback)
.setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qS).setTransitionToFullShadeProgress(eq(true), anyFloat(), anyFloat())
+ verify(qsTransitionController).dragDownAmount = 10f
verify(depthController).transitionToFullShadeProgress = anyFloat()
}
@@ -473,8 +525,8 @@
transitionController.dragDownAmount = 10f
- verify(nsslController).setOverScrollAmount(0)
- verify(scrimController, never()).setNotificationsOverScrollAmount(anyInt())
+ verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(splitShadeOverScroller)
}
@Test
@@ -483,8 +535,8 @@
transitionController.dragDownAmount = 10f
- verify(nsslController).setOverScrollAmount(0)
- verify(scrimController).setNotificationsOverScrollAmount(0)
+ verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(singleShadeOverScroller)
}
@Test
@@ -545,10 +597,11 @@
)
interface TestComponent {
- val transitionController: LockscreenShadeTransitionController
-
val configurationController: FakeConfigurationController
val disableFlagsRepository: FakeDisableFlagsRepository
+ val powerInteractor: PowerInteractor
+ val shadeInteractor: ShadeInteractor
+ val shadeRepository: FakeShadeRepository
val testScope: TestScope
@Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 3412679..a4c12f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +41,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -67,10 +72,12 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mListener = new NotificationListener(
mContext,
mNotificationManager,
+ new SilentNotificationStatusIconsVisibilityInteractor(
+ new NotificationListenerSettingsRepository()),
mFakeSystemClock,
mFakeExecutor,
mPluginManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 764f7b6..560ebc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -33,9 +33,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import org.junit.Before;
import org.junit.Test;
@@ -87,7 +88,8 @@
new RemoteInputControllerLogger(logcatLogBuffer()),
mClickNotifier,
new ActionClickLogger(logcatLogBuffer()),
- mock(DumpManager.class));
+ mock(JavaAdapter.class),
+ mock(ShadeInteractor.class));
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -145,7 +147,8 @@
RemoteInputControllerLogger remoteInputControllerLogger,
NotificationClickNotifier clickNotifier,
ActionClickLogger actionClickLogger,
- DumpManager dumpManager) {
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor) {
super(
context,
notifPipelineFlags,
@@ -158,7 +161,8 @@
remoteInputControllerLogger,
clickNotifier,
actionClickLogger,
- dumpManager);
+ javaAdapter,
+ shadeInteractor);
}
public void setUpWithPresenterForTest(Callback callback,
@@ -170,3 +174,4 @@
}
}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 3327e42..d6dfc5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -23,9 +23,30 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+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.flags.FakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
+import com.android.systemui.util.mockito.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -40,17 +61,22 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class StatusBarStateControllerImplTest : SysuiTestCase() {
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private lateinit var shadeInteractor: ShadeInteractor
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock private lateinit var mockDarkAnimator: ObjectAnimator
- @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var mockDarkAnimator: ObjectAnimator
private lateinit var controller: StatusBarStateControllerImpl
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -64,11 +90,75 @@
uiEventLogger = UiEventLoggerFake()
controller = object : StatusBarStateControllerImpl(
uiEventLogger,
- mock(DumpManager::class.java),
- interactionJankMonitor, shadeExpansionStateManager
+ interactionJankMonitor,
+ mock(),
+ { shadeInteractor }
) {
override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator }
}
+
+ val powerInteractor = PowerInteractor(
+ FakePowerRepository(),
+ FalsingCollectorFake(),
+ mock(),
+ controller)
+ val keyguardRepository = FakeKeyguardRepository()
+ val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ val featureFlags = FakeFeatureFlagsClassic()
+ val shadeRepository = FakeShadeRepository()
+ val sceneContainerFlags = FakeSceneContainerFlags()
+ val configurationRepository = FakeConfigurationRepository()
+ val keyguardInteractor = KeyguardInteractor(
+ keyguardRepository,
+ FakeCommandQueue(),
+ powerInteractor,
+ featureFlags,
+ sceneContainerFlags,
+ FakeKeyguardBouncerRepository(),
+ configurationRepository,
+ shadeRepository,
+ utils::sceneInteractor)
+ val keyguardTransitionInteractor = KeyguardTransitionInteractor(
+ testScope.backgroundScope,
+ keyguardTransitionRepository,
+ { keyguardInteractor },
+ { fromLockscreenTransitionInteractor },
+ { fromPrimaryBouncerTransitionInteractor })
+ fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor(
+ keyguardTransitionRepository,
+ keyguardTransitionInteractor,
+ testScope.backgroundScope,
+ keyguardInteractor,
+ featureFlags,
+ shadeRepository,
+ powerInteractor)
+ fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
+ keyguardTransitionRepository,
+ keyguardTransitionInteractor,
+ testScope.backgroundScope,
+ keyguardInteractor,
+ featureFlags,
+ mock(),
+ mock(),
+ powerInteractor)
+ shadeInteractor = ShadeInteractor(
+ testScope.backgroundScope,
+ FakeDeviceProvisioningRepository(),
+ FakeDisableFlagsRepository(),
+ mock(),
+ sceneContainerFlags,
+ utils::sceneInteractor,
+ keyguardRepository,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ FakeUserSetupRepository(),
+ mock(),
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ ResourcesSplitShadeStateController()),
+ shadeRepository,
+ )
}
@Test
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 235ac5c..6059363 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..7f5384d
--- /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/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 546abd4..6c1f537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -39,10 +39,10 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.mockito.any
@@ -246,7 +246,7 @@
unseenFilter.onCleanup()
// THEN: The SeenNotificationProvider has been updated to reflect the suppression
- assertThat(notificationListInteractor.hasFilteredOutSeenNotifications.value).isTrue()
+ assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
}
}
@@ -597,7 +597,8 @@
FakeSettings().apply {
putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
}
- val notificationListInteractor = NotificationListInteractor(NotificationListRepository())
+ val seenNotificationsInteractor =
+ SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
KeyguardCoordinator(
testDispatcher,
@@ -610,7 +611,7 @@
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
- notificationListInteractor,
+ seenNotificationsInteractor,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
@@ -618,7 +619,7 @@
KeyguardCoordinatorTestScope(
keyguardCoordinator,
testScope,
- notificationListInteractor,
+ seenNotificationsInteractor,
fakeSettings,
)
.testBlock()
@@ -628,7 +629,7 @@
private inner class KeyguardCoordinatorTestScope(
private val keyguardCoordinator: KeyguardCoordinator,
private val scope: TestScope,
- val notificationListInteractor: NotificationListInteractor,
+ val seenNotificationsInteractor: SeenNotificationsInteractor,
private val fakeSettings: FakeSettings,
) : CoroutineScope by scope {
val testScheduler: TestCoroutineScheduler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 655bd72..e81207e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -19,6 +19,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.setFlagValue
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -27,6 +28,8 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
@@ -36,9 +39,10 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -52,27 +56,47 @@
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
@Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
+ @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
@Before
fun setUp() {
initMocks(this)
- coordinator = StackCoordinator(groupExpansionManagerImpl, notificationIconAreaController)
+ setUp(NotificationIconContainerRefactor.FLAG_NAME to null)
+ entry = NotificationEntryBuilder().setSection(section).build()
+ }
+
+ private fun setUp(vararg flags: Pair<String, Boolean?>) {
+ flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) }
+ reset(pipeline)
+ coordinator =
+ StackCoordinator(
+ groupExpansionManagerImpl,
+ notificationIconAreaController,
+ renderListInteractor,
+ )
coordinator.attach(pipeline)
afterRenderListListener = withArgCaptor {
verify(pipeline).addOnAfterRenderListListener(capture())
}
- entry = NotificationEntryBuilder().setSection(section).build()
}
@Test
fun testUpdateNotificationIcons() {
+ setUp(NotificationIconContainerRefactor.FLAG_NAME to false)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
}
@Test
+ fun testSetRenderedListOnInteractor() {
+ setUp(NotificationIconContainerRefactor.FLAG_NAME to true)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
+ }
+
+ @Test
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
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/data/repository/NotificationExpansionRepositoryTest.kt
deleted file mode 100644
index f28d9ab..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-
-@SmallTest
-class NotificationExpansionRepositoryTest : SysuiTestCase() {
- private val underTest = NotificationExpansionRepository()
-
- @Test
- fun setIsExpandAnimationRunning_startsAsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun setIsExpandAnimationRunning_false_emitsTrue() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
- underTest.setIsExpandAnimationRunning(true)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun setIsExpandAnimationRunning_false_emitsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
- underTest.setIsExpandAnimationRunning(true)
-
- // WHEN the animation is no longer running
- underTest.setIsExpandAnimationRunning(false)
-
- // THEN the flow emits false
- assertThat(latest).isFalse()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
new file mode 100644
index 0000000..683d0aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.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.statusbar.notification.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class NotificationAlertsInteractorTest : SysuiTestCase() {
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationAlertsInteractor
+ val disableFlags: FakeDisableFlagsRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerNotificationAlertsInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+ }
+
+ @Test
+ fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
new file mode 100644
index 0000000..a0faab5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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 NotificationLaunchAnimationInteractorTest : SysuiTestCase() {
+ private val repository = NotificationLaunchAnimationRepository()
+ private val underTest = NotificationLaunchAnimationInteractor(repository)
+
+ @Test
+ fun setIsLaunchAnimationRunning_startsAsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun setIsLaunchAnimationRunning_false_emitsTrue() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+ underTest.setIsLaunchAnimationRunning(true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun setIsLaunchAnimationRunning_false_emitsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+ underTest.setIsLaunchAnimationRunning(true)
+
+ // WHEN the animation is no longer running
+ underTest.setIsLaunchAnimationRunning(false)
+
+ // THEN the flow emits false
+ assertThat(latest).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
deleted file mode 100644
index fe49016..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import android.app.StatusBarManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class NotificationsInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: NotificationsInteractor
-
- private val testScope = TestScope(UnconfinedTestDispatcher())
- private val commandQueue: CommandQueue = mock()
- private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
- private val disableFlagsLogger = DisableFlagsLogger()
- private lateinit var disableFlagsRepository: DisableFlagsRepository
-
- @Before
- fun setUp() {
- disableFlagsRepository =
- DisableFlagsRepositoryImpl(
- commandQueue,
- DISPLAY_ID,
- testScope.backgroundScope,
- mock(),
- logBuffer,
- disableFlagsLogger,
- )
- underTest = NotificationsInteractor(disableFlagsRepository)
- }
-
- @Test
- fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
- }
-
- @Test
- fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
- }
-
- private fun getCommandQueueCallback(): CommandQueue.Callbacks {
- val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
- verify(commandQueue).addCallback(callbackCaptor.capture())
- return callbackCaptor.value
- }
-
- private companion object {
- const val DISPLAY_ID = 1
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
new file mode 100644
index 0000000..ca8ea4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.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.collection.ListEntry
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class RenderNotificationsListInteractorTest : SysuiTestCase() {
+
+ private val notifsRepository = ActiveNotificationListRepository()
+ private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository)
+ private val underTest =
+ RenderNotificationListInteractor(
+ notifsRepository,
+ sectionStyleProvider = mock(),
+ )
+
+ @Test
+ fun setRenderedList_preservesOrdering() = runTest {
+ val notifs by collectLastValue(notifsInteractor.notifications)
+ val keys = (1..50).shuffled().map { "$it" }
+ val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+ underTest.setRenderedList(entries)
+ assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2a3c1a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val repository = ActiveNotificationListRepository()
+ private val underTest = SeenNotificationsInteractor(repository)
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index f72142f..cc87d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -22,8 +22,15 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
@@ -44,9 +52,13 @@
FooterView mView;
+ Context mSpyContext = spy(mContext);
+
@Before
public void setUp() {
- mView = (FooterView) LayoutInflater.from(mContext).inflate(
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
+
+ mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
R.layout.status_bar_notification_footer, null, false);
mView.setDuration(0);
}
@@ -102,6 +114,37 @@
}
@Test
+ public void testSetMessageString_resourceOnlyFetchedOnce() {
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.unlock_prompt_footer))
+ .getText().toString()).contains("Unlock");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ public void testSetMessageIcon_resourceOnlyFetchedOnce() {
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+
+ clearInvocations(mSpyContext);
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+
+ verify(mSpyContext, never()).getDrawable(anyInt());
+ }
+
+ @Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
new file mode 100644
index 0000000..57a7c3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.footer.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FooterViewModelTest : SysuiTestCase() {
+ private val repository = ActiveNotificationListRepository()
+ private val interactor = SeenNotificationsInteractor(repository)
+ private val underTest = FooterViewModel(interactor)
+
+ @Test
+ fun testMessageVisible_whenFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = true
+
+ assertThat(message?.visible).isTrue()
+ }
+
+ @Test
+ fun testMessageVisible_whenNoFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(message?.visible).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
new file mode 100644
index 0000000..ec80e5f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -0,0 +1,418 @@
+/*
+ * 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.icon.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
+import com.android.systemui.statusbar.notification.shared.byIsAmbient
+import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
+import com.android.systemui.statusbar.notification.shared.byIsPulsing
+import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
+import com.android.systemui.statusbar.notification.shared.byIsSilent
+import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).containsExactlyElementsIn(testIcons)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showDismissed = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: AlwaysOnDisplayNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val deviceEntryRepository: FakeDeviceEntryRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: StatusBarNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+}
+
+private val testIcons =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ isAmbient = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ isRowDismissed = true,
+ ),
+ activeNotificationModel(
+ key = "notif4",
+ isSilent = true,
+ ),
+ activeNotificationModel(
+ key = "notif5",
+ isLastMessageFromReply = true,
+ ),
+ activeNotificationModel(
+ key = "notif6",
+ isSuppressedFromStatusBar = true,
+ ),
+ activeNotificationModel(
+ key = "notif7",
+ isPulsing = true,
+ ),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
deleted file mode 100644
index e57986d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.SysUITestModule
-import com.android.TestMocksModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
-
- @Mock private lateinit var dozeParams: DozeParameters
-
- private lateinit var testComponent: TestComponent
- private val underTest
- get() = testComponent.underTest
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- allowTestableLooperAsMainThread()
-
- testComponent =
- DaggerNotificationIconAreaControllerViewBinderWrapperImplTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FACE_AUTH_REFACTOR, value = false)
- set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- ),
- )
- }
-
- @Test
- fun testNotificationIcons_settingHideIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
- assertFalse(underTest.shouldShowLowPriorityIcons())
- }
-
- @Test
- fun testNotificationIcons_settingShowIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
- assertTrue(underTest.shouldShowLowPriorityIcons())
- }
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
-
- val underTest: NotificationIconAreaControllerViewBinderWrapperImpl
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 31efebb..41c7071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -44,7 +44,9 @@
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -243,6 +245,7 @@
)
)
val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
@@ -266,6 +269,7 @@
fun isDozing_startAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -274,13 +278,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_startDozeTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -289,13 +295,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isFalse()
}
@Test
fun isDozing_startDozeToAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.DOZING,
@@ -304,13 +312,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isNotDozing_startAodToGoneTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -319,13 +329,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+ assertThat(isDozing?.value).isFalse()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_stopAnimation() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -335,7 +347,8 @@
)
runCurrent()
- underTest.completeDozeAnimation()
+ assertThat(isDozing?.isAnimating).isEqualTo(true)
+ isDozing?.stopAnimating()
runCurrent()
assertThat(isDozing?.isAnimating).isEqualTo(false)
@@ -345,6 +358,7 @@
fun isNotVisible_pulseExpanding() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
runCurrent()
@@ -355,6 +369,7 @@
fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
to = KeyguardState.GONE,
@@ -364,13 +379,15 @@
whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_bypassEnabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
deviceEntryRepository.setBypassEnabled(true)
runCurrent()
@@ -381,6 +398,7 @@
fun isNotVisible_pulseExpanding_notBypassing() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -398,26 +416,30 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(false)
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -425,7 +447,8 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
@@ -440,13 +463,15 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_stopAnimation() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -454,7 +479,8 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- underTest.completeVisibilityAnimation()
+ assertThat(isVisible?.isAnimating).isEqualTo(true)
+ isVisible?.stopAnimating()
runCurrent()
assertThat(isVisible?.isAnimating).isEqualTo(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index e1e7f92..ba68fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.SysUITestModule
@@ -34,13 +36,23 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -61,18 +73,6 @@
@Mock lateinit var dozeParams: DozeParameters
private lateinit var testComponent: TestComponent
- private val underTest: NotificationIconContainerStatusBarViewModel
- get() = testComponent.underTest
- private val deviceProvisioningRepository
- get() = testComponent.deviceProvisioningRepository
- private val keyguardTransitionRepository
- get() = testComponent.keyguardTransitionRepository
- private val keyguardRepository
- get() = testComponent.keyguardRepository
- private val powerRepository
- get() = testComponent.powerRepository
- private val scope
- get() = testComponent.scope
@Before
fun setup() {
@@ -82,7 +82,6 @@
DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
.create(
test = this,
- // Configurable bindings
featureFlags =
FakeFeatureFlagsClassicModule {
set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -93,155 +92,299 @@
dozeParameters = dozeParams,
),
)
-
- keyguardRepository.setKeyguardShowing(false)
- deviceProvisioningRepository.setFactoryResetProtectionActive(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
+ .apply {
+ keyguardRepository.setKeyguardShowing(false)
+ deviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ }
}
@Test
fun animationsEnabled_isFalse_whenFrpIsActive() =
- scope.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ deviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_AOD,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_AOD,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_PULSING,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_PULSING,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
- scope.runTest {
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ with(testComponent) {
+ scope.runTest {
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isFalse()
- keyguardRepository.setKeyguardShowing(false)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(false)
+ runCurrent()
- assertThat(animationsEnabled).isTrue()
+ assertThat(animationsEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun iconColors_testsDarkBounds() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ assertThat(iconColorsLookup).isNotNull()
+
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ assertThat(iconColors).isNotNull()
+ iconColors!!
+
+ assertThat(iconColors.tint).isEqualTo(0xAABBCC)
+
+ val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+
+ assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_nonColorized() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(), isColorized = false)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
+ assertThat(iconColors).isNull()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_animateOnAppear_shadeCollapsed() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(0f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isTrue()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isFalse()
+ }
}
@SysUISingleton
@@ -249,7 +392,6 @@
modules =
[
SysUITestModule::class,
- // Real impls
BiometricsDomainLayerModule::class,
UserDomainLayerModule::class,
]
@@ -258,10 +400,14 @@
val underTest: NotificationIconContainerStatusBarViewModel
+ val activeNotificationsRepository: ActiveNotificationListRepository
+ val darkIconRepository: FakeDarkIconRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
+ val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
val keyguardRepository: FakeKeyguardRepository
val powerRepository: FakePowerRepository
+ val shadeRepository: FakeShadeRepository
val scope: TestScope
@Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index cbb0894..947bcfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -2,7 +2,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
@@ -19,7 +18,30 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {
+class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
+ override val provider: VisualInterruptionDecisionProvider
+ get() =
+ NotificationInterruptStateProviderWrapper(
+ NotificationInterruptStateProviderImpl(
+ context.contentResolver,
+ powerManager,
+ ambientDisplayConfiguration,
+ batteryController,
+ statusBarStateController,
+ keyguardStateController,
+ headsUpManager,
+ logger,
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger,
+ userTracker,
+ deviceProvisionedController
+ )
+ .also { it.mUseHeadsUp = true }
+ )
+
+ // Tests of internals of the wrapper:
@Test
fun decisionOfTrue() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
new file mode 100644
index 0000000..6f4bbd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -0,0 +1,221 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.app.ActivityManager
+import android.app.Notification
+import android.app.Notification.BubbleMetadata
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_MUTABLE
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.hardware.display.FakeAmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.statusbar.FakeStatusBarStateController
+import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.`when` as whenever
+
+abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
+ private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
+
+ protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
+ protected val batteryController = FakeBatteryController(leakCheck)
+ protected val deviceProvisionedController: DeviceProvisionedController = mock()
+ protected val flags: NotifPipelineFlags = mock()
+ protected val headsUpManager: HeadsUpManager = mock()
+ protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
+ mock()
+ protected val keyguardStateController: KeyguardStateController = mock()
+ protected val logger: NotificationInterruptLogger = mock()
+ protected val mainHandler: Handler = mock()
+ protected val powerManager: PowerManager = mock()
+ protected val statusBarStateController = FakeStatusBarStateController()
+ protected val uiEventLogger = UiEventLoggerFake()
+ protected val userTracker = FakeUserTracker()
+
+ protected abstract val provider: VisualInterruptionDecisionProvider
+
+ @Before
+ fun setUp() {
+ val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+ userTracker.set(listOf(user), /* currentUserIndex = */ 0)
+
+ whenever(headsUpManager.isSnoozed(any())).thenReturn(false)
+ whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+ .thenReturn(false)
+ }
+
+ @Test
+ fun testShouldPeek() {
+ ensureStateForPeek()
+
+ assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt)
+ }
+
+ @Test
+ fun testShouldPulse() {
+ ensureStateForPulse()
+
+ assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt)
+ }
+
+ @Test
+ fun testShouldFsi_awake() {
+ ensureStateForAwakeFsi()
+
+ assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ }
+
+ @Test
+ fun testShouldFsi_dreaming() {
+ ensureStateForDreamingFsi()
+
+ assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ }
+
+ @Test
+ fun testShouldFsi_keyguard() {
+ ensureStateForKeyguardFsi()
+
+ assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ }
+
+ @Test
+ fun testShouldBubble() {
+ assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt)
+ }
+
+ private fun ensureStateForPeek() {
+ whenever(powerManager.isScreenOn).thenReturn(true)
+ statusBarStateController.dozing = false
+ statusBarStateController.dreaming = false
+ }
+
+ private fun ensureStateForPulse() {
+ ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true
+ batteryController.setIsAodPowerSave(false)
+ statusBarStateController.dozing = true
+ }
+
+ private fun ensureStateForAwakeFsi() {
+ whenever(powerManager.isInteractive).thenReturn(false)
+ statusBarStateController.dreaming = false
+ statusBarStateController.state = SHADE
+ }
+
+ private fun ensureStateForDreamingFsi() {
+ whenever(powerManager.isInteractive).thenReturn(true)
+ statusBarStateController.dreaming = true
+ statusBarStateController.state = SHADE
+ }
+
+ private fun ensureStateForKeyguardFsi() {
+ whenever(powerManager.isInteractive).thenReturn(true)
+ statusBarStateController.dreaming = false
+ statusBarStateController.state = KEYGUARD
+ }
+
+ private fun createNotif(
+ hasFsi: Boolean = false,
+ bubbleMetadata: BubbleMetadata? = null
+ ): Notification {
+ return Notification.Builder(context, TEST_CHANNEL_ID)
+ .apply {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+
+ if (hasFsi) {
+ setFullScreenIntent(mock(), /* highPriority = */ true)
+ }
+
+ if (bubbleMetadata != null) {
+ setBubbleMetadata(bubbleMetadata)
+ }
+ }
+ .setContentTitle(TEST_CONTENT_TITLE)
+ .setContentText(TEST_CONTENT_TEXT)
+ .build()
+ }
+
+ private fun createBubbleMetadata(): BubbleMetadata {
+ val pendingIntent =
+ PendingIntent.getActivity(
+ context,
+ /* requestCode = */ 0,
+ Intent().setPackage(context.packageName),
+ FLAG_MUTABLE
+ )
+
+ val icon = Icon.createWithResource(context.resources, R.drawable.android)
+
+ return BubbleMetadata.Builder(pendingIntent, icon).build()
+ }
+
+ private fun createEntry(
+ notif: Notification,
+ importance: Int = IMPORTANCE_DEFAULT,
+ canBubble: Boolean? = null
+ ): NotificationEntry {
+ return NotificationEntryBuilder()
+ .apply {
+ setPkg(TEST_PACKAGE)
+ setOpPkg(TEST_PACKAGE)
+ setTag(TEST_TAG)
+ setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+ setNotification(notif)
+ setImportance(importance)
+
+ if (canBubble != null) {
+ setCanBubble(canBubble)
+ }
+ }
+ .build()
+ }
+
+ private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH)
+
+ private fun createPulseEntry() =
+ createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also {
+ modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()
+ }
+
+ private fun createFsiEntry() =
+ createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH)
+
+ private fun createBubbleEntry() =
+ createEntry(
+ notif = createNotif(bubbleMetadata = createBubbleMetadata()),
+ importance = IMPORTANCE_HIGH,
+ canBubble = true
+ )
+}
+
+private const val TEST_CONTENT_TITLE = "Test Content Title"
+private const val TEST_CONTENT_TEXT = "Test content text"
+private const val TEST_CHANNEL_ID = "test_channel"
+private const val TEST_CHANNEL_NAME = "Test Channel"
+private const val TEST_PACKAGE = "test_package"
+private const val TEST_TAG = "test_tag"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index a0f5048..4bb28ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -50,8 +50,8 @@
@Test
fun testViewWalker_plainNotification_withPublicView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
val row =
testHelper.createRow(
@@ -122,9 +122,9 @@
@Test
fun testViewWalker_bigPictureNotification() {
- val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+ val bigPicture = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888))
val row =
testHelper.createRow(
Notification.Builder(mContext)
@@ -182,8 +182,8 @@
@Test
fun testViewWalker_customView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
index ccef1d5..9b9cb82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
@@ -61,8 +61,9 @@
else -> null
} as T?
}
- mNormalColor =
- Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface)
+
+ mNormalColor = Utils.getColorAttrDefaultColor(mContext,
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index 23ae26c..1bb7b61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -24,9 +24,9 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.widget.NotificationDrawableConsumer
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -45,6 +45,7 @@
import org.mockito.Mockito.verifyZeroInteractions
private const val FREE_IMAGE_DELAY_MS = 4000L
+private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,6 +82,7 @@
@Before
fun setUp() {
allowTestableLooperAsMainThread()
+ overrideMaxImageSizes()
iconManager =
BigPictureIconManager(
context,
@@ -430,6 +432,17 @@
verifyZeroInteractions(mockConsumer)
}
+ private fun overrideMaxImageSizes() {
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_width,
+ MAX_IMAGE_SIZE
+ )
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_height,
+ MAX_IMAGE_SIZE
+ )
+ }
+
private fun assertIsPlaceHolder(drawable: Drawable) {
assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
new file mode 100644
index 0000000..ca105f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.shared
+
+import android.graphics.drawable.Icon
+import com.google.common.truth.Correspondence
+
+val byKey: Correspondence<ActiveNotificationModel, String> =
+ Correspondence.transforming({ it?.key }, "has a key of")
+val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of")
+val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isSuppressedFromStatusBar },
+ "has an isSuppressedFromStatusBar value of",
+ )
+val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isSilent }, "has an isSilent value of")
+val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of")
+val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isLastMessageFromReply },
+ "has an isLastMessageFromReply value of"
+ )
+val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+
+fun activeNotificationModel(
+ key: String,
+ groupKey: String? = null,
+ isAmbient: Boolean = false,
+ isRowDismissed: Boolean = false,
+ isSilent: Boolean = false,
+ isLastMessageFromReply: Boolean = false,
+ isSuppressedFromStatusBar: Boolean = false,
+ isPulsing: Boolean = false,
+ aodIcon: Icon? = null,
+ shelfIcon: Icon? = null,
+ statusBarIcon: Icon? = null,
+) =
+ ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 390c1dd..02a67d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -21,23 +21,25 @@
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
-import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
-import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -55,98 +57,118 @@
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
- // mocks
@Mock private lateinit var keyguardTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
- @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- // fakes
- private val keyguardRepository = FakeKeyguardRepository()
- private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
- private val a11yRepo = FakeAccessibilityRepository()
- private val powerRepository = FakePowerRepository()
- private val powerInteractor by lazy {
- PowerInteractorFactory.create(
- repository = powerRepository,
- screenOffAnimationController = screenOffAnimationController,
- statusBarStateController = statusBarStateController,
- )
- .powerInteractor
- }
-
- // real impls
- private val a11yInteractor = AccessibilityInteractor(a11yRepo)
- private val activatableViewModel = ActivatableNotificationViewModel(a11yInteractor)
- private val interactor by lazy {
- NotificationShelfInteractor(
- keyguardRepository,
- deviceEntryFaceAuthRepository,
- powerInteractor,
- keyguardTransitionController,
- )
- }
- private val underTest by lazy { NotificationShelfViewModel(interactor, activatableViewModel) }
+ private lateinit var testComponent: TestComponent
@Before
fun setUp() {
whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+ testComponent =
+ DaggerNotificationShelfViewModelTest_TestComponent.factory()
+ .create(
+ test = this,
+ mocks =
+ TestMocksModule(
+ lockscreenShadeTransitionController = keyguardTransitionController,
+ screenOffAnimationController = screenOffAnimationController,
+ statusBarStateController = statusBarStateController,
+ )
+ )
}
@Test
- fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
- val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+ fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
+ with(testComponent) {
+ testScope.runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
- keyguardRepository.setKeyguardShowing(false)
+ keyguardRepository.setKeyguardShowing(false)
- assertThat(canModifyNotifColor).isTrue()
- }
+ assertThat(canModifyNotifColor).isTrue()
+ }
+ }
@Test
- fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
- val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+ fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() =
+ with(testComponent) {
+ testScope.runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
- keyguardRepository.setKeyguardShowing(true)
- deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
- assertThat(canModifyNotifColor).isTrue()
- }
+ assertThat(canModifyNotifColor).isTrue()
+ }
+ }
@Test
- fun cannotModifyColorOfNotifications_whenBypass() = runTest {
- val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+ fun cannotModifyColorOfNotifications_whenBypass() =
+ with(testComponent) {
+ testScope.runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
- keyguardRepository.setKeyguardShowing(true)
- deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
- assertThat(canModifyNotifColor).isFalse()
- }
+ assertThat(canModifyNotifColor).isFalse()
+ }
+ }
@Test
- fun isClickable_whenKeyguardShowing() = runTest {
- val isClickable by collectLastValue(underTest.isClickable)
+ fun isClickable_whenKeyguardShowing() =
+ with(testComponent) {
+ testScope.runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
- keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardShowing(true)
- assertThat(isClickable).isTrue()
- }
+ assertThat(isClickable).isTrue()
+ }
+ }
@Test
- fun isNotClickable_whenKeyguardNotShowing() = runTest {
- val isClickable by collectLastValue(underTest.isClickable)
+ fun isNotClickable_whenKeyguardNotShowing() =
+ with(testComponent) {
+ testScope.runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
- keyguardRepository.setKeyguardShowing(false)
+ keyguardRepository.setKeyguardShowing(false)
- assertThat(isClickable).isFalse()
- }
+ assertThat(isClickable).isFalse()
+ }
+ }
@Test
- fun onClicked_goesToLockedShade() {
- whenever(statusBarStateController.isDozing).thenReturn(true)
+ fun onClicked_goesToLockedShade() =
+ with(testComponent) {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
- underTest.onShelfClicked()
+ underTest.onShelfClicked()
- assertThat(powerRepository.lastWakeReason).isNotNull()
- assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
- verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
+ assertThat(powerRepository.lastWakeReason).isNotNull()
+ assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+ verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
+ }
+
+ @Component(modules = [SysUITestModule::class, ActivatableNotificationViewModelModule::class])
+ @SysUISingleton
+ interface TestComponent {
+
+ val underTest: NotificationShelfViewModel
+ val deviceEntryFaceAuthRepository: FakeDeviceEntryFaceAuthRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val powerRepository: FakePowerRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 20197e3..39e3d5da3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
@@ -82,13 +83,14 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -171,8 +173,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
- private final NotificationListInteractor mNotificationListInteractor =
- new NotificationListInteractor(new NotificationListRepository());
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor =
+ new SeenNotificationsInteractor(new ActiveNotificationListRepository());
private NotificationStackScrollLayoutController mController;
@@ -504,7 +506,7 @@
@Test
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
- mNotificationListInteractor.setHasFilteredOutSeenNotifications(true);
+ mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
verify(mNotificationStackScrollLayout).updateFooter();
@@ -704,7 +706,7 @@
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- mNotificationListInteractor,
+ mSeenNotificationsInteractor,
mShadeController,
mJankMonitor,
mStackLogger,
@@ -716,8 +718,9 @@
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
- new ResourcesSplitShadeStateController()
- );
+ new ResourcesSplitShadeStateController(),
+ mock(ConfigurationState.class),
+ mock(ShelfNotificationIconViewStore.class));
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 033c96a..8f36d4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -19,6 +19,8 @@
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
+import static com.android.systemui.Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -165,6 +167,11 @@
mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
+ // Some tests in this file test the FooterView. Since we're refactoring the FooterView
+ // business logic out of the NSSL, the behavior tested in this file will eventually be
+ // tested directly in the new FooterView stack. For now, we just want to make sure that the
+ // old behavior is preserved when the flag is off.
+ setFlagDefault(mSetFlagsRule, FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 49906dc..08ef477 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -62,6 +62,10 @@
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val maxPanelHeight =
+ mContext.resources.displayMetrics.heightPixels -
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -147,7 +151,7 @@
stackScrollAlgorithm.initView(context)
hostView.removeAllViews()
hostView.addView(emptyShadeView)
- ambientState.layoutMaxHeight = 1280
+ ambientState.layoutMaxHeight = maxPanelHeight.toInt()
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
@@ -155,7 +159,7 @@
context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
- assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
+ assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
}
@Test
@@ -356,7 +360,7 @@
whenever(notificationRow.canViewBeCleared()).thenReturn(false)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -370,7 +374,7 @@
whenever(notificationRow.canViewBeCleared()).thenReturn(true)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -382,7 +386,7 @@
fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.removeAllViews() // remove all rows
hostView.addView(footerView)
@@ -1006,7 +1010,7 @@
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
- val stackTopMargin = 50
+ val stackTopMargin = bigGap.toInt() // a gap smaller than the headsUpInset
val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
// we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
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 68f2728..7de05ad 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.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.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 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 @@
@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 cfd220b..164325a 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 @@
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 a59cd87..6478a3e 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
@@ -21,6 +21,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -158,12 +159,12 @@
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;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
@@ -186,8 +187,6 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -201,6 +200,8 @@
import javax.inject.Provider;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -336,7 +337,7 @@
mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
// For the Shade to respond to Back gesture, we must enable the event routing
mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
// For the Shade to animate during the Back gesture, we must enable the animation flag.
@@ -502,7 +503,6 @@
(Lazy<NotificationPresenter>) () -> mNotificationPresenter,
(Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter,
mNotifLaunchAnimControllerProvider,
- new NotificationExpansionRepository(),
mDozeParameters,
mScrimController,
mBiometricUnlockControllerLazy,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 593c587..19215e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +44,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -51,6 +54,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -96,18 +100,21 @@
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
-
@Mock private DozeInteractor mDozeInteractor;
+
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
- mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
- mBatteryController, mScrimController, () -> mBiometricUnlockController,
- () -> mAssistManager, mDozeScrimController,
- mKeyguardUpdateMonitor, mPulseExpansionHandler,
- mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mAuthController, mNotificationIconAreaController, mDozeInteractor);
+ mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags,
+ mHeadsUpManager, mBatteryController, mScrimController,
+ () -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
+ mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
+ mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+ mDozeInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index d84bb72..1fad2a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +36,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeHeadsUpTracker;
@@ -42,8 +45,10 @@
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
@@ -82,10 +87,12 @@
private KeyguardStateController mKeyguardStateController;
private CommandQueue mCommandQueue;
private NotificationRoundnessManager mNotificationRoundnessManager;
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mTestHelper = new NotificationTestHelper(
mContext,
mDependency,
@@ -119,6 +126,8 @@
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags,
+ mock(HeadsUpNotificationIconInteractor.class),
Optional.of(mOperatorNameView));
mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
}
@@ -203,6 +212,7 @@
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
Optional.empty());
assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index cda2a74..48b95d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -34,7 +34,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.kotlin.JavaAdapter;
import org.junit.After;
import org.junit.Before;
@@ -56,6 +57,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import kotlinx.coroutines.flow.StateFlowKt;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -70,8 +73,9 @@
@Mock private KeyguardBypassController mBypassController;
@Mock private ConfigurationControllerImpl mConfigurationController;
@Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private UiEventLogger mUiEventLogger;
+ @Mock private JavaAdapter mJavaAdapter;
+ @Mock private ShadeInteractor mShadeInteractor;
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -85,7 +89,8 @@
Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
- ShadeExpansionStateManager shadeExpansionStateManager
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor
) {
super(
context,
@@ -98,7 +103,8 @@
handler,
accessibilityManagerWrapper,
uiEventLogger,
- shadeExpansionStateManager
+ javaAdapter,
+ shadeInteractor
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -117,7 +123,8 @@
mTestHandler,
mAccessibilityManagerWrapper,
mUiEventLogger,
- mShadeExpansionStateManager
+ mJavaAdapter,
+ mShadeInteractor
);
}
@@ -129,6 +136,7 @@
@Before
@Override
public void setUp() {
+ when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
final AccessibilityManagerWrapper accessibilityMgr =
mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 6209f73..bd0dbee 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 @@
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 @@
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 6484389..361df1c 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 @@
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/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 1b8cfd4..92e40df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
@@ -28,12 +30,14 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.SetFlagsRuleExtensionsKt;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -82,6 +86,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
mController = new LegacyNotificationIconAreaControllerImpl(
mContext,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index c282c1e..2b28562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -20,12 +20,15 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.setFlagDefault
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -41,6 +44,11 @@
private val iconContainer = NotificationIconContainer(context, /* attrs= */ null)
+ @Before
+ fun setup() {
+ mSetFlagsRule.setFlagDefault(NotificationIconContainerRefactor.FLAG_NAME)
+ }
+
@Test
fun calculateWidthFor_zeroIcons_widthIsZero() {
assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f),
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 beac995..1e31977 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.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 @@
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/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index ee4f208..53c621d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -53,7 +53,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -79,8 +79,8 @@
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
private final ShadeController mShadeController = mock(ShadeController.class);
- private final NotificationsInteractor mNotificationsInteractor =
- mock(NotificationsInteractor.class);
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor =
+ mock(NotificationAlertsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
private final InitController mInitController = new InitController();
@@ -116,7 +116,7 @@
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
- mNotificationsInteractor,
+ mNotificationAlertsInteractor,
mock(LockscreenShadeTransitionController.class),
mock(PowerInteractor.class),
mCommandQueue,
@@ -226,7 +226,7 @@
.setTag("a")
.setNotification(n)
.build();
- when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+ when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
assertTrue("When alerts aren't enabled, interruptions are suppressed",
mInterruptSuppressor.suppressInterruptions(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index 21d22bc..1e628bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -13,13 +13,17 @@
*/
package com.android.systemui.statusbar.phone
+import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
@@ -31,6 +35,7 @@
class SystemUIBottomSheetDialogTest : SysuiTestCase() {
private val configurationController = mock<ConfigurationController>()
+ private val config = mock<Configuration>()
private lateinit var dialog: SystemUIBottomSheetDialog
@@ -53,4 +58,23 @@
verify(configurationController).removeCallback(any())
}
+
+ @Test
+ fun onConfigurationChanged_calledInSubclass() {
+ var onConfigChangedCalled = false
+ val subclass =
+ object : SystemUIBottomSheetDialog(mContext, configurationController) {
+ override fun onConfigurationChanged() {
+ onConfigChangedCalled = true
+ }
+ }
+
+ subclass.show()
+
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ captor.value.onConfigChanged(config)
+
+ assertThat(onConfigChangedCalled).isTrue()
+ }
}
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 bcb34d6..d1b9b8a 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.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,27 +37,29 @@
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;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.common.ui.ConfigurationState;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -72,6 +69,7 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -276,15 +274,15 @@
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
@@ -316,7 +314,7 @@
// 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());
}
@@ -332,7 +330,7 @@
// 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());
}
@@ -349,7 +347,7 @@
// 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
@@ -360,7 +358,7 @@
// 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());
}
@@ -374,7 +372,7 @@
// 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());
}
@@ -388,7 +386,7 @@
// 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());
}
@@ -402,7 +400,7 @@
// 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
@@ -411,7 +409,7 @@
// 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());
}
@@ -444,7 +442,7 @@
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
@@ -500,6 +498,20 @@
}
@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);
@@ -682,7 +694,7 @@
mLocationPublisher,
mMockNotificationAreaController,
mShadeExpansionStateManager,
- mock(FeatureFlags.class),
+ mock(FeatureFlagsClassic.class),
mStatusBarIconController,
mIconManagerFactory,
mCollapsedStatusBarViewModel,
@@ -702,7 +714,12 @@
mExecutor,
mDumpManager,
mStatusBarWindowStateController,
- mKeyguardUpdateMonitor);
+ mKeyguardUpdateMonitor,
+ mock(NotificationIconContainerStatusBarViewModel.class),
+ mock(ConfigurationState.class),
+ mock(ConfigurationController.class),
+ mock(StatusBarNotificationIconViewStore.class),
+ mock(DemoModeController.class));
}
private void setUpDaggerComponent() {
@@ -715,18 +732,7 @@
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 e6b09e3..5c960b6 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 @@
}
@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/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index b78e839..63de068 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -23,26 +23,27 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyString
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.Date
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class VariableDateViewControllerTest : SysuiTestCase() {
@@ -57,12 +58,15 @@
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
private lateinit var view: VariableDateView
+ @Mock
+ private lateinit var shadeInteractor: ShadeInteractor
@Captor
private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
+ private val qsExpansion = MutableStateFlow(0F)
+
private var lastText: String? = null
- private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
private lateinit var systemClock: FakeSystemClock
private lateinit var testableLooper: TestableLooper
private lateinit var testableHandler: Handler
@@ -80,7 +84,7 @@
systemClock = FakeSystemClock()
systemClock.setCurrentTimeMillis(TIME_STAMP)
- shadeExpansionStateManager = ShadeExpansionStateManager()
+ `when`(shadeInteractor.qsExpansion).thenReturn(qsExpansion)
`when`(view.longerPattern).thenReturn(LONG_PATTERN)
`when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
@@ -91,6 +95,7 @@
Unit
}
`when`(view.isAttachedToWindow).thenReturn(true)
+ `when`(view.viewTreeObserver).thenReturn(mock())
val date = Date(TIME_STAMP)
longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
@@ -103,12 +108,12 @@
}
controller = VariableDateViewController(
- systemClock,
- broadcastDispatcher,
- shadeExpansionStateManager,
- mock(),
- testableHandler,
- view
+ systemClock,
+ broadcastDispatcher,
+ shadeInteractor,
+ mock(),
+ testableHandler,
+ view
)
controller.init()
@@ -180,7 +185,7 @@
@Test
fun testQsExpansionTrue_ignoreAtMostMeasureRequests() {
- shadeExpansionStateManager.onQsExpansionFractionChanged(0f)
+ qsExpansion.value = 0f
onMeasureListenerCaptor.value.onMeasureAction(
getTextLength(shortText).toInt(),
@@ -195,7 +200,7 @@
@Test
fun testQsExpansionFalse_acceptAtMostMeasureRequests() {
- shadeExpansionStateManager.onQsExpansionFractionChanged(1f)
+ qsExpansion.value = 1f
onMeasureListenerCaptor.value.onMeasureAction(
getTextLength(shortText).toInt(),
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 1bc346d..96db09e 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 @@
PowerInteractorFactory.create().powerInteractor,
FakeFeatureFlagsClassic(),
sceneTestUtils.sceneContainerFlags,
- sceneTestUtils.deviceEntryRepository,
FakeKeyguardBouncerRepository(),
FakeConfigurationRepository(),
FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index aaf8d07..94100fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.util.ui
import android.testing.AndroidTestingRunner
@@ -20,9 +22,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -35,64 +36,193 @@
@Test
fun animatableEvent_updatesValue() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = false))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
fun animatableEvent_startAnimation() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
events.emit(AnimatableEvent(value = 2, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatedValue_stopAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- stopEvent.emit(Unit)
+ assertThat(value?.isAnimating).isTrue()
+ value?.stopAnimating()
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
- fun animatedValue_stopAnimating_notAnimating() = runTest {
+ fun animatedValue_stopAnimatingPrevValue_doesNothing() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
- values.launchIn(backgroundScope)
+ val values = events.toAnimatedValueFlow()
+ val value by collectLastValue(values)
runCurrent()
- events.emit(AnimatableEvent(value = 1, startAnimating = false))
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+ val prevValue = value
+ assertThat(prevValue?.isAnimating).isTrue()
- assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+ events.emit(AnimatableEvent(value = 2, startAnimating = true))
+ assertThat(value?.isAnimating).isTrue()
+ prevValue?.stopAnimating()
+
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun zipValues_applyTransform() {
+ val animating = AnimatedValue.Animating(1) {}
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.value).isEqualTo(3)
+ }
+
+ @Test
+ fun zipValues_firstIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_secondIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(notAnimating, animating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_bothAnimating_resultIsAnimating() {
+ var firstStopped = false
+ var secondStopped = false
+ val first = AnimatedValue.Animating(1) { firstStopped = true }
+ val second = AnimatedValue.Animating(2) { secondStopped = true }
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(firstStopped).isTrue()
+ assertThat(secondStopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_neitherAnimating_resultIsNotAnimating() {
+ val first = AnimatedValue.NotAnimating(1)
+ val second = AnimatedValue.NotAnimating(2)
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isFalse()
+ }
+
+ @Test
+ fun mapAnimatedValue_isAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(3) { stopped = true }
+ val squared = animating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isTrue()
+ squared.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun mapAnimatedValue_notAnimating() {
+ val notAnimating = AnimatedValue.NotAnimating(3)
+ val squared = notAnimating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_neitherAnimating() {
+ val nested = AnimatedValue.NotAnimating(AnimatedValue.NotAnimating(10))
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_outerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.NotAnimating(10)
+ val nested = AnimatedValue.Animating(inner) { stopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_innerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.Animating(10) { stopped = true }
+ val nested = AnimatedValue.NotAnimating(inner)
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_bothAnimating() {
+ var innerStopped = false
+ var outerStopped = false
+ val inner = AnimatedValue.Animating(10) { innerStopped = true }
+ val nested = AnimatedValue.Animating(inner) { outerStopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(innerStopped).isTrue()
+ assertThat(outerStopped).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c4c7472..7456e00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -26,6 +26,8 @@
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
@@ -40,6 +42,9 @@
import android.app.KeyguardManager;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.SystemClock;
import android.provider.Settings;
@@ -52,6 +57,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageButton;
import androidx.test.core.view.MotionEventBuilder;
import androidx.test.filters.SmallTest;
@@ -90,6 +96,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
import java.util.function.Predicate;
@SmallTest
@@ -757,6 +764,86 @@
foundCaptionLog);
}
+ @Test
+ public void turnOnDnD_volumeSliderIconChangesToDnd() {
+ State state = createShellState();
+ state.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
+
+ mDialog.onStateChangedH(state);
+ mTestableLooper.processAllMessages();
+
+ boolean foundDnDIcon = findDndIconAmongVolumeRows();
+ assertTrue(foundDnDIcon);
+ }
+
+ @Test
+ public void turnOffDnD_volumeSliderIconIsNotDnd() {
+ State state = createShellState();
+ state.zenMode = Settings.Global.ZEN_MODE_OFF;
+
+ mDialog.onStateChangedH(state);
+ mTestableLooper.processAllMessages();
+
+ boolean foundDnDIcon = findDndIconAmongVolumeRows();
+ assertFalse(foundDnDIcon);
+ }
+
+ /**
+ * @return true if at least one volume row has the DND icon
+ */
+ private boolean findDndIconAmongVolumeRows() {
+ ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows);
+ assumeNotNull(volumeDialogRows);
+ Drawable expected = getContext().getDrawable(com.android.internal.R.drawable.ic_qs_dnd);
+ boolean foundDnDIcon = false;
+ final int rowCount = volumeDialogRows.getChildCount();
+ // we don't make assumptions about the position of the dnd row
+ for (int i = 0; i < rowCount && !foundDnDIcon; i++) {
+ View volumeRow = volumeDialogRows.getChildAt(i);
+ ImageButton rowIcon = volumeRow.findViewById(R.id.volume_row_icon);
+ assertNotNull(rowIcon);
+
+ // VolumeDialogImpl changes tint and alpha in a private method, so we clear those here.
+ rowIcon.setImageTintList(null);
+ rowIcon.setAlpha(0xFF);
+
+ Drawable actual = rowIcon.getDrawable();
+ foundDnDIcon |= areDrawablesEqual(expected, actual);
+ }
+ return foundDnDIcon;
+ }
+
+ private boolean areDrawablesEqual(Drawable drawable1, Drawable drawable2) {
+ int size = 100;
+ Bitmap bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Bitmap bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas1 = new Canvas(bm1);
+ Canvas canvas2 = new Canvas(bm2);
+
+ drawable1.setBounds(0, 0, size, size);
+ drawable2.setBounds(0, 0, size, size);
+
+ drawable1.draw(canvas1);
+ drawable2.draw(canvas2);
+
+ boolean areBitmapsEqual = areBitmapsEqual(bm1, bm2);
+ bm1.recycle();
+ bm2.recycle();
+ return areBitmapsEqual;
+ }
+
+ private boolean areBitmapsEqual(Bitmap a, Bitmap b) {
+ if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
+ int w = a.getWidth();
+ int h = a.getHeight();
+ int[] aPix = new int[w * h];
+ int[] bPix = new int[w * h];
+ a.getPixels(aPix, 0, w, 0, 0, w, h);
+ b.getPixels(bPix, 0, w, 0, 0, w, h);
+ return Arrays.equals(aPix, bPix);
+ }
+
@After
public void teardown() {
// Detailed logs to track down timeout issues in b/299491332
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 a42fa41..ec808c7 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.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 @@
powerInteractor,
featureFlags,
sceneContainerFlags,
- new FakeDeviceEntryRepository(),
new FakeKeyguardBouncerRepository(),
configurationRepository,
shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt
new file mode 100644
index 0000000..cdd0ff7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt
@@ -0,0 +1,68 @@
+package android.hardware.display
+
+import android.content.Context
+
+class FakeAmbientDisplayConfiguration(context: Context) : AmbientDisplayConfiguration(context) {
+ var fakePulseOnNotificationEnabled = true
+
+ override fun pulseOnNotificationEnabled(user: Int) = fakePulseOnNotificationEnabled
+
+ override fun pulseOnNotificationAvailable() = TODO("Not yet implemented")
+
+ override fun pickupGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun dozePickupSensorAvailable() = TODO("Not yet implemented")
+
+ override fun tapGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun tapSensorAvailable() = TODO("Not yet implemented")
+
+ override fun doubleTapGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun doubleTapSensorAvailable() = TODO("Not yet implemented")
+
+ override fun quickPickupSensorEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun screenOffUdfpsEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun wakeScreenGestureAvailable() = TODO("Not yet implemented")
+
+ override fun wakeLockScreenGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun wakeDisplayGestureEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun getWakeLockScreenDebounce() = TODO("Not yet implemented")
+
+ override fun doubleTapSensorType() = TODO("Not yet implemented")
+
+ override fun tapSensorTypeMapping() = TODO("Not yet implemented")
+
+ override fun longPressSensorType() = TODO("Not yet implemented")
+
+ override fun udfpsLongPressSensorType() = TODO("Not yet implemented")
+
+ override fun quickPickupSensorType() = TODO("Not yet implemented")
+
+ override fun pulseOnLongPressEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun alwaysOnEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun alwaysOnAvailable() = TODO("Not yet implemented")
+
+ override fun alwaysOnAvailableForUser(user: Int) = TODO("Not yet implemented")
+
+ override fun ambientDisplayComponent() = TODO("Not yet implemented")
+
+ override fun accessibilityInversionEnabled(user: Int) = TODO("Not yet implemented")
+
+ override fun ambientDisplayAvailable() = TODO("Not yet implemented")
+
+ override fun dozeSuppressed(user: Int) = TODO("Not yet implemented")
+
+ override fun disableDozeSettings(userId: Int) = TODO("Not yet implemented")
+
+ override fun disableDozeSettings(shouldDisableNonUserConfigurable: Boolean, userId: Int) =
+ TODO("Not yet implemented")
+
+ override fun restoreDozeSettings(userId: Int) = TODO("Not yet implemented")
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 6ef812b..29e737e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,9 +15,8 @@
*/
package com.android.systemui;
-import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -38,11 +37,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
-import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.UserTracker;
import org.junit.After;
import org.junit.AfterClass;
@@ -53,7 +48,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.Future;
/**
@@ -71,7 +65,7 @@
new AndroidXAnimatorIsolationRule();
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
@@ -86,7 +80,7 @@
public TestableDependency mDependency;
private Instrumentation mRealInstrumentation;
- private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
+ private SysuiTestDependency mSysuiDependency;
@Before
public void SysuiSetup() throws Exception {
@@ -94,21 +88,8 @@
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
- // Set the value of a single gantry flag inside the com.android.systemui package to
- // ensure all flags in that package are faked (and thus require a value to be set).
- mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
-
- mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);
- mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
- mContext,
- mContext.getMainExecutor(),
- mock(Looper.class),
- mock(Executor.class),
- mock(DumpManager.class),
- mock(BroadcastDispatcherLogger.class),
- mock(UserTracker.class),
- shouldFailOnLeakedReceiver());
-
+ mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
+ mDependency = mSysuiDependency.install();
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
Instrumentation inst = spy(mRealInstrumentation);
when(inst.getContext()).thenAnswer(invocation -> {
@@ -120,11 +101,6 @@
"SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
});
InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
- // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
- // record receivers registered. They are not actually leaked as they are kept just as a weak
- // reference and are never sent to the Context. This will also prevent a real
- // BroadcastDispatcher from actually registering receivers.
- mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
}
protected boolean shouldFailOnLeakedReceiver() {
@@ -144,8 +120,9 @@
}
disallowTestableLooperAsMainThread();
mContext.cleanUpReceivers(this.getClass().getSimpleName());
- if (mFakeBroadcastDispatcher != null) {
- mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName());
+ FakeBroadcastDispatcher dispatcher = getFakeBroadcastDispatcher();
+ if (dispatcher != null) {
+ dispatcher.cleanUpReceivers(this.getClass().getSimpleName());
}
}
@@ -172,7 +149,7 @@
}
public FakeBroadcastDispatcher getFakeBroadcastDispatcher() {
- return mFakeBroadcastDispatcher;
+ return mSysuiDependency.getFakeBroadcastDispatcher();
}
public SysuiTestableContext getContext() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
index c791f4f..d89d7b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
@@ -1,26 +1,60 @@
package com.android.systemui
import android.annotation.SuppressLint
-import android.content.Context
+import android.os.Looper
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.fakeDialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import java.util.concurrent.Executor
+import org.mockito.Mockito.mock
-@SuppressLint("VisibleForTests")
-fun installSysuiTestDependency(context: Context): TestableDependency {
- val initializer: SystemUIInitializer = SystemUIInitializerImpl(context)
- initializer.init(true)
+class SysuiTestDependency(
+ val context: SysuiTestableContext,
+ private val shouldFailOnLeakedReceiver: Boolean
+) {
+ var fakeBroadcastDispatcher: FakeBroadcastDispatcher? = null
- val dependency = TestableDependency(initializer.sysUIComponent.createDependency())
- Dependency.setInstance(dependency)
+ @SuppressLint("VisibleForTests")
+ fun install(): TestableDependency {
+ val initializer: SystemUIInitializer = SystemUIInitializerImpl(context)
+ initializer.init(true)
- dependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
+ val dependency = TestableDependency(initializer.sysUIComponent.createDependency())
+ Dependency.setInstance(dependency)
- // Make sure that all tests on any SystemUIDialog does not crash because this dependency
- // is missing (constructing the actual one would throw).
- // TODO(b/219008720): Remove this.
- dependency.injectMockDependency(SystemUIDialogManager::class.java)
- dependency.injectTestDependency(DialogLaunchAnimator::class.java, fakeDialogLaunchAnimator())
- return dependency
+ dependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
+
+ // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+ // is missing (constructing the actual one would throw).
+ // TODO(b/219008720): Remove this.
+ dependency.injectMockDependency(SystemUIDialogManager::class.java)
+ dependency.injectTestDependency(
+ DialogLaunchAnimator::class.java,
+ fakeDialogLaunchAnimator()
+ )
+
+ // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
+ // record receivers registered. They are not actually leaked as they are kept just as a weak
+ // reference and are never sent to the Context. This will also prevent a real
+ // BroadcastDispatcher from actually registering receivers.
+ fakeBroadcastDispatcher =
+ FakeBroadcastDispatcher(
+ context,
+ context.mainExecutor,
+ mock(Looper::class.java),
+ mock(Executor::class.java),
+ mock(DumpManager::class.java),
+ mock(BroadcastDispatcherLogger::class.java),
+ mock(UserTracker::class.java),
+ shouldFailOnLeakedReceiver
+ )
+ dependency.injectTestDependency(BroadcastDispatcher::class.java, fakeBroadcastDispatcher)
+ return dependency
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.kt
new file mode 100644
index 0000000..baf1006
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/FakeAccessibilityDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.accessibility.data
+
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeAccessibilityRepositoryModule::class])
+object FakeAccessibilityDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 8444c7b..4085b1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -16,8 +16,20 @@
package com.android.systemui.accessibility.data.repository
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
+@SysUISingleton
class FakeAccessibilityRepository(
- override val isTouchExplorationEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : AccessibilityRepository
+ override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+) : AccessibilityRepository {
+ @Inject constructor() : this(MutableStateFlow(false))
+}
+
+@Module
+interface FakeAccessibilityRepositoryModule {
+ @Binds fun bindFake(fake: FakeAccessibilityRepository): AccessibilityRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 5dcc742..8353cf7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -34,7 +34,10 @@
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
- suspend fun onAnyConfigurationChange() {
+ private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
+ private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
+
+ fun onAnyConfigurationChange() {
_onAnyConfigurationChange.tryEmit(Unit)
}
@@ -42,12 +45,12 @@
_scaleForResolution.value = scale
}
- override fun getResolutionScale(): Float {
- return _scaleForResolution.value
- }
+ override fun getResolutionScale(): Float = _scaleForResolution.value
- override fun getDimensionPixelSize(id: Int): Int {
- return 0
+ override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 0
+
+ fun setDimensionPixelSize(id: Int, pixelSize: Int) {
+ pixelSizes.getOrPut(id) { MutableStateFlow(pixelSize) }.value = pixelSize
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index cffbf02..36f0882 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.data
+import com.android.systemui.accessibility.data.FakeAccessibilityDataLayerModule
import com.android.systemui.authentication.data.FakeAuthenticationDataLayerModule
import com.android.systemui.bouncer.data.repository.FakeBouncerDataLayerModule
import com.android.systemui.common.ui.data.FakeCommonDataLayerModule
@@ -30,6 +31,7 @@
@Module(
includes =
[
+ FakeAccessibilityDataLayerModule::class,
FakeAuthenticationDataLayerModule::class,
FakeBouncerDataLayerModule::class,
FakeCommonDataLayerModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 5cd09d8..5fd0b4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -43,9 +43,10 @@
mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
/** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository() : DisplayRepository {
- private val flow = MutableSharedFlow<Set<Display>>()
- private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
+class FakeDisplayRepository : DisplayRepository {
+ private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
+ private val pendingDisplayFlow =
+ MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
@@ -59,7 +60,7 @@
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
- private val _displayChangeEvent = MutableSharedFlow<Int>()
+ private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
index abf72af..6710072 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data
import com.android.systemui.keyguard.data.repository.FakeCommandQueueModule
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepositoryModule
import dagger.Module
@@ -24,6 +25,7 @@
includes =
[
FakeCommandQueueModule::class,
+ FakeDeviceEntryFaceAuthRepositoryModule::class,
FakeKeyguardRepositoryModule::class,
FakeKeyguardTransitionRepositoryModule::class,
]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 322fb28..e289083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,15 +17,20 @@
package com.android.systemui.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+@SysUISingleton
+class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
@@ -66,3 +71,8 @@
_runningAuthRequest.value = null
}
}
+
+@Module
+interface FakeDeviceEntryFaceAuthRepositoryModule {
+ @Binds fun bindFake(fake: FakeDeviceEntryFaceAuthRepository): DeviceEntryFaceAuthRepository
+}
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 fae49b1..88a88c7 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 @@
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 82ce802..d2ff9bc5 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 @@
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 @@
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 @@
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
- deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -67,7 +64,6 @@
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
- deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -87,7 +83,6 @@
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 f62bf60..1efa74b 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 @@
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 1cb4ab7..2b3330f 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,65 +16,39 @@
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
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
class FakeQSTileDataInteractor<T>(
- private val dataFlow: MutableSharedFlow<FakeData<T>> =
- MutableSharedFlow(replay = Int.MAX_VALUE),
+ private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE),
private val availabilityFlow: MutableSharedFlow<Boolean> =
MutableSharedFlow(replay = Int.MAX_VALUE),
) : QSTileDataInteractor<T> {
- private val mutableDataRequests = mutableListOf<QSTileDataRequest>()
- val dataRequests: List<QSTileDataRequest> = mutableDataRequests
+ private val mutableDataRequests = mutableListOf<DataRequest>()
+ val dataRequests: List<DataRequest> = mutableDataRequests
- private val mutableAvailabilityRequests = mutableListOf<Unit>()
- val availabilityRequests: List<Unit> = mutableAvailabilityRequests
+ private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>()
+ val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests
- @CheckReturnValue
- fun emitData(data: T): FilterEmit =
- object : FilterEmit {
- override fun forRequest(request: QSTileDataRequest): Boolean =
- dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request)))
- override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any))
- }
+ @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data)
fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
- override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> {
- mutableDataRequests.add(qsTileDataRequest)
- return dataFlow
- .filter {
- when (it.filter) {
- is DataFilter.Any -> true
- is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest
- }
- }
- .map { it.data }
+ override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> {
+ mutableDataRequests.add(DataRequest(user))
+ return triggers.flatMapLatest { dataFlow }
}
- override fun availability(): Flow<Boolean> {
- mutableAvailabilityRequests.add(Unit)
+ override fun availability(user: UserHandle): Flow<Boolean> {
+ mutableAvailabilityRequests.add(AvailabilityRequest(user))
return availabilityFlow
}
- interface FilterEmit {
- fun forRequest(request: QSTileDataRequest): Boolean
- fun forAnyRequest(): Boolean
- }
-
- class FakeData<T>(
- val data: T,
- val filter: DataFilter,
- )
-
- sealed class DataFilter {
- object Any : DataFilter()
- class ForRequest(val request: QSTileDataRequest) : DataFilter()
- }
+ data class DataRequest(val user: UserHandle)
+ data class AvailabilityRequest(val user: UserHandle)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 9c99cb5..597d52d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -16,22 +16,19 @@
package com.android.systemui.qs.tiles.base.interactor
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
private val mutex: Mutex = Mutex()
- private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf()
+ private val mutableInputs: MutableList<QSTileInput<T>> = mutableListOf()
- val inputs: List<FakeInput<T>> = mutableInputs
+ val inputs: List<QSTileInput<T>> = mutableInputs
- fun lastInput(): FakeInput<T>? = inputs.lastOrNull()
+ fun lastInput(): QSTileInput<T>? = inputs.lastOrNull()
- override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) {
- mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) }
+ override suspend fun handleInput(input: QSTileInput<T>) {
+ mutex.withLock { mutableInputs.add(input) }
}
-
- data class FakeInput<T>(val userAction: QSTileUserAction, val data: T)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
new file mode 100644
index 0000000..de72a7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+class FakeQSTileConfigProvider : QSTileConfigProvider {
+
+ private val configs: MutableMap<String, QSTileConfig> = mutableMapOf()
+
+ override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec)
+
+ fun putConfig(tileSpec: TileSpec, config: QSTileConfig) {
+ configs[tileSpec.spec] = config
+ }
+
+ fun removeConfig(tileSpec: TileSpec): QSTileConfig? = configs.remove(tileSpec.spec)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
index 201926d..2a0ee88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
@@ -16,10 +16,7 @@
package com.android.systemui.qs.tiles.viewmodel
-import androidx.annotation.StringRes
import com.android.internal.logging.InstanceId
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
object QSTileConfigTestBuilder {
@@ -29,8 +26,7 @@
class BuildingScope {
var tileSpec: TileSpec = TileSpec.create("test_spec")
- var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0))
- @StringRes var tileLabel: Int = 0
+ var uiConfig: QSTileUIConfig = QSTileUIConfig.Empty
var instanceId: InstanceId = InstanceId.fakeInstanceId(0)
var metricsSpec: String = tileSpec.spec
var policy: QSTilePolicy = QSTilePolicy.NoRestrictions
@@ -38,8 +34,7 @@
fun build() =
QSTileConfig(
tileSpec,
- tileIcon,
- tileLabel,
+ uiConfig,
instanceId,
metricsSpec,
policy,
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 a4881bc..17384351 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 @@
commandQueue = FakeCommandQueue(),
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
- deviceEntryRepository = FakeDeviceEntryRepository(),
bouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository = FakeConfigurationRepository(),
shadeRepository = FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
new file mode 100644
index 0000000..19fdb6d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
@@ -0,0 +1,159 @@
+package com.android.systemui.statusbar
+
+import android.view.View
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+class FakeStatusBarStateController : SysuiStatusBarStateController {
+ @JvmField var state = StatusBarState.SHADE
+
+ @JvmField var upcomingState = StatusBarState.SHADE
+
+ @JvmField var lastState = StatusBarState.SHADE
+
+ @JvmField var dozing = false
+
+ @JvmField var expanded = false
+
+ @JvmField var pulsing = false
+
+ @JvmField var dreaming = false
+
+ @JvmField var dozeAmount = 0.0f
+
+ @JvmField var interpolatedDozeAmount = 0.0f
+
+ @JvmField var dozeAmountTarget = 0.0f
+
+ @JvmField var leaveOpen = false
+
+ @JvmField var keyguardRequested = false
+
+ var lastSetDozeAmountView: View? = null
+ private set
+
+ var lastSetDozeAmountAnimated = false
+ private set
+
+ var lastSystemBarAppearance = 0
+ private set
+
+ var lastSystemBarBehavior = 0
+ private set
+
+ var lastSystemBarRequestedVisibleTypes = 0
+ private set
+
+ var lastSystemBarPackageName: String? = null
+ private set
+
+ private val _callbacks = mutableSetOf<StatusBarStateController.StateListener>()
+
+ @JvmField val callbacks: Set<StatusBarStateController.StateListener> = _callbacks
+
+ private var fullscreen = false
+
+ override fun start() {}
+
+ override fun getState() = state
+
+ override fun setState(newState: Int, force: Boolean): Boolean {
+ val oldState = this.state
+ newState != oldState || force || return false
+
+ callbacks.forEach { it.onStatePreChange(oldState, newState) }
+ this.lastState = oldState
+ this.state = newState
+ setUpcomingState(newState)
+ callbacks.forEach { it.onStateChanged(newState) }
+ callbacks.forEach { it.onStatePostChange() }
+ return true
+ }
+
+ override fun getCurrentOrUpcomingState() = upcomingState
+
+ override fun setUpcomingState(upcomingState: Int) {
+ upcomingState != this.upcomingState || return
+ this.upcomingState = upcomingState
+ callbacks.forEach { it.onUpcomingStateChanged(upcomingState) }
+ }
+
+ override fun isDozing() = dozing
+
+ override fun setIsDozing(dozing: Boolean): Boolean {
+ dozing != this.dozing || return false
+ this.dozing = dozing
+ callbacks.forEach { it.onDozingChanged(dozing) }
+ return true
+ }
+
+ override fun isExpanded() = expanded
+
+ fun fakeShadeExpansionFullyChanged(expanded: Boolean) {
+ expanded != this.expanded || return
+ this.expanded = expanded
+ callbacks.forEach { it.onExpandedChanged(expanded) }
+ }
+
+ override fun isPulsing() = pulsing
+
+ override fun setPulsing(pulsing: Boolean) {
+ pulsing != this.pulsing || return
+ this.pulsing = pulsing
+ callbacks.forEach { it.onPulsingChanged(pulsing) }
+ }
+
+ override fun isDreaming() = dreaming
+
+ override fun setIsDreaming(drreaming: Boolean): Boolean {
+ dreaming != this.dreaming || return false
+ this.dreaming = dreaming
+ callbacks.forEach { it.onDreamingChanged(dreaming) }
+ return true
+ }
+
+ override fun getDozeAmount() = dozeAmount
+
+ override fun setAndInstrumentDozeAmount(view: View?, dozeAmount: Float, animated: Boolean) {
+ dozeAmountTarget = dozeAmount
+ lastSetDozeAmountView = view
+ lastSetDozeAmountAnimated = animated
+ if (!animated) {
+ this.dozeAmount = dozeAmount
+ }
+ }
+
+ override fun leaveOpenOnKeyguardHide() = leaveOpen
+
+ override fun setLeaveOpenOnKeyguardHide(leaveOpen: Boolean) {
+ this.leaveOpen = leaveOpen
+ }
+
+ override fun getInterpolatedDozeAmount() = interpolatedDozeAmount
+
+ fun fakeInterpolatedDozeAmountChanged(interpolatedDozeAmount: Float) {
+ this.interpolatedDozeAmount = interpolatedDozeAmount
+ callbacks.forEach { it.onDozeAmountChanged(dozeAmount, interpolatedDozeAmount) }
+ }
+
+ override fun goingToFullShade() = state == StatusBarState.SHADE && leaveOpen
+
+ override fun fromShadeLocked() = lastState == StatusBarState.SHADE_LOCKED
+
+ override fun isKeyguardRequested(): Boolean = keyguardRequested
+
+ override fun setKeyguardRequested(keyguardRequested: Boolean) {
+ this.keyguardRequested = keyguardRequested
+ }
+
+ override fun addCallback(listener: StatusBarStateController.StateListener?) {
+ _callbacks.add(listener!!)
+ }
+
+ override fun addCallback(listener: StatusBarStateController.StateListener?, rank: Int) {
+ throw RuntimeException("addCallback with rank unsupported")
+ }
+
+ override fun removeCallback(listener: StatusBarStateController.StateListener?) {
+ _callbacks.remove(listener!!)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index e59f642..8f18e13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -15,8 +15,10 @@
*/
package com.android.systemui.statusbar.data
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositoryModule
import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
+import com.android.systemui.statusbar.phone.data.FakeStatusBarPhoneDataLayerModule
import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
import dagger.Module
@@ -25,7 +27,9 @@
includes =
[
FakeStatusBarDisableFlagsDataLayerModule::class,
+ FakeStatusBarModeRepositoryModule::class,
FakeStatusBarNotificationsDataLayerModule::class,
+ FakeStatusBarPhoneDataLayerModule::class,
FakeStatusBarPipelineDataLayerModule::class,
FakeStatusBarPolicyDataLayerModule::class,
]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 61ba464..f25d282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -18,17 +18,17 @@
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeStatusBarModeRepository : StatusBarModeRepository {
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
override val isTransientShown = MutableStateFlow(false)
override val isInFullscreenMode = MutableStateFlow(false)
override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
-
override fun showTransient() {
isTransientShown.value = true
}
@@ -36,3 +36,8 @@
isTransientShown.value = false
}
}
+
+@Module
+interface FakeStatusBarModeRepositoryModule {
+ @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
new file mode 100644
index 0000000..d2c3b7a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.phone.data
+
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeDarkIconRepositoryModule::class]) object FakeStatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
new file mode 100644
index 0000000..50d3f0a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
+ override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0))
+}
+
+@Module
+interface FakeDarkIconRepositoryModule {
+ @Binds fun bindFake(fake: FakeDarkIconRepository): DarkIconRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index eaa109d..209cac6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -25,6 +25,7 @@
public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
implements BatteryController {
+ private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
public FakeBatteryController(LeakCheck test) {
@@ -63,7 +64,7 @@
@Override
public boolean isAodPowerSave() {
- return false;
+ return mIsAodPowerSave;
}
@Override
@@ -71,6 +72,10 @@
return mWirelessCharging;
}
+ public void setIsAodPowerSave(boolean isAodPowerSave) {
+ mIsAodPowerSave = isAodPowerSave;
+ }
+
public void setWirelessCharging(boolean wirelessCharging) {
mWirelessCharging = wirelessCharging;
}
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 1866a99..67c2caa 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -16,9 +16,12 @@
package android.renderscript;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.res.Resources;
+import android.util.Slog;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -35,6 +38,15 @@
private static final String TAG = "ScriptC";
/**
+ * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported
+ * and an exception is thrown when the class is instantiated.
+ * In targetSdkVersion 34 and below, Renderscript's ScriptC still works.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = 35)
+ private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L;
+
+ /**
* Only intended for use by the generated derived classes.
*
* @param id
@@ -89,7 +101,19 @@
setID(id);
}
+ private static void throwExceptionIfSDKTooHigh() {
+ String message =
+ "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer "
+ + "to https://developer.android.com/guide/topics/renderscript/migration-guide "
+ + "for proposed alternatives.";
+ Slog.w(TAG, message);
+ if (CompatChanges.isChangeEnabled(RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID)) {
+ throw new UnsupportedOperationException(message);
+ }
+ }
+
private static synchronized long internalCreate(RenderScript rs, Resources resources, int resourceID) {
+ throwExceptionIfSDKTooHigh();
byte[] pgm;
int pgmLength;
InputStream is = resources.openRawResource(resourceID);
@@ -126,6 +150,7 @@
private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
// Log.v(TAG, "Create script for resource = " + resName);
+ throwExceptionIfSDKTooHigh();
return rs.nScriptCCreate(resName, RenderScript.getCachePath(), bitcode, bitcode.length);
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 3ae9360..aca8409 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -238,12 +238,14 @@
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/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 5af80da..2913716 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -145,6 +145,13 @@
/** Flag for intercepting generic motion events. */
static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800;
+ /**
+ * Flag for enabling the two-finger triple-tap magnification feature.
+ *
+ * @see #setUserAndEnabledFeatures(int, int)
+ */
+ static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000;
+
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 87f9cf1..a7b5325 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.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 @@
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) {
@@ -2798,6 +2786,12 @@
flags |= AccessibilityInputFilter
.FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP;
}
+ if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+ if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) {
+ flags |= AccessibilityInputFilter
+ .FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP;
+ }
+ }
if (userState.isShortcutMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
@@ -3162,6 +3156,21 @@
return false;
}
+ private boolean readMagnificationTwoFingerTripleTapSettingsLocked(
+ AccessibilityUserState userState) {
+ final boolean magnificationTwoFingerTripleTapEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ 0, userState.mUserId) == 1;
+ if ((magnificationTwoFingerTripleTapEnabled
+ != userState.isMagnificationTwoFingerTripleTapEnabledLocked())) {
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(
+ magnificationTwoFingerTripleTapEnabled);
+ return true;
+ }
+ return false;
+ }
+
private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) {
final boolean autoclickEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
@@ -3397,6 +3406,7 @@
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+ || userState.isMagnificationTwoFingerTripleTapEnabledLocked()
|| userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
@@ -4932,6 +4942,9 @@
private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure
.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+ private final Uri mMagnificationTwoFingerTripleTapEnabledUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED);
+
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -4989,6 +5002,10 @@
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri,
false, this, UserHandle.USER_ALL);
+ if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+ contentResolver.registerContentObserver(mMagnificationTwoFingerTripleTapEnabledUri,
+ false, this, UserHandle.USER_ALL);
+ }
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -5039,6 +5056,11 @@
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && mMagnificationTwoFingerTripleTapEnabledUri.equals(uri)) {
+ if (readMagnificationTwoFingerTripleTapSettingsLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mAutoclickEnabledUri.equals(uri)) {
if (readAutoclickEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index b4efec1..68ee780 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -110,6 +110,7 @@
private boolean mIsAudioDescriptionByDefaultRequested;
private boolean mIsAutoclickEnabled;
private boolean mIsMagnificationSingleFingerTripleTapEnabled;
+ private boolean mMagnificationTwoFingerTripleTapEnabled;
private boolean mIsFilterKeyEventsEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -212,6 +213,7 @@
mRequestTwoFingerPassthrough = false;
mSendMotionEventsEnabled = false;
mIsMagnificationSingleFingerTripleTapEnabled = false;
+ mMagnificationTwoFingerTripleTapEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -633,6 +635,14 @@
mIsMagnificationSingleFingerTripleTapEnabled = enabled;
}
+ public boolean isMagnificationTwoFingerTripleTapEnabledLocked() {
+ return mMagnificationTwoFingerTripleTapEnabled;
+ }
+
+ public void setMagnificationTwoFingerTripleTapEnabledLocked(boolean enabled) {
+ mMagnificationTwoFingerTripleTapEnabled = enabled;
+ }
+
public boolean isFilterKeyEventsEnabledLocked() {
return mIsFilterKeyEventsEnabled;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index ed77476..2032a50 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 @@
private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
mAppsOnVirtualDeviceListener;
+ private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener;
+
/**
* Callbacks into AccessibilityManagerService.
*/
@@ -189,6 +191,9 @@
}
}
}
+ if (mProxyA11yServiceConnections.size() == 1) {
+ registerVirtualDeviceListener();
+ }
}
// If the client dies, make sure to remove the connection.
@@ -210,6 +215,31 @@
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 @@
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 423b85f..4688658 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 @@
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 @@
*/
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 @@
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 @@
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 @@
}
public void destroy() {
- cancelCurrentRequest();
unbind();
- mCallbacks = null;
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 65975e4..76ebdf4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -103,9 +103,8 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
-import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -207,9 +206,6 @@
boolean mDevCfgEnableContentProtectionReceiver;
@GuardedBy("mLock")
- int mDevCfgContentProtectionAppsBlocklistSize;
-
- @GuardedBy("mLock")
int mDevCfgContentProtectionBufferSize;
@GuardedBy("mLock")
@@ -237,7 +233,7 @@
@Nullable private final ComponentName mContentProtectionServiceComponentName;
- @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+ @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
@Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
@@ -287,17 +283,15 @@
if (getEnableContentProtectionReceiverLocked()) {
mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
if (mContentProtectionServiceComponentName != null) {
- mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
- mContentProtectionBlocklistManager.updateBlocklist(
- mDevCfgContentProtectionAppsBlocklistSize);
+ mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
mContentProtectionConsentManager = createContentProtectionConsentManager();
} else {
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
} else {
mContentProtectionServiceComponentName = null;
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
}
@@ -445,8 +439,6 @@
case ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
- case ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
@@ -502,14 +494,6 @@
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
- mDevCfgContentProtectionAppsBlocklistSize =
- DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
- ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
- // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
- // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -542,7 +526,7 @@
+ mDevCfgMaxBufferSize
+ ", idleFlush="
+ mDevCfgIdleFlushingFrequencyMs
- + ", textFluxh="
+ + ", textFlush="
+ mDevCfgTextChangeFlushingFrequencyMs
+ ", logHistory="
+ mDevCfgLogHistorySize
@@ -552,8 +536,6 @@
+ mDevCfgDisableFlushForViewTreeAppearing
+ ", enableContentProtectionReceiver="
+ mDevCfgEnableContentProtectionReceiver
- + ", contentProtectionAppsBlocklistSize="
- + mDevCfgContentProtectionAppsBlocklistSize
+ ", contentProtectionBufferSize="
+ mDevCfgContentProtectionBufferSize
+ ", contentProtectionRequiredGroupsConfig="
@@ -844,9 +826,6 @@
pw.print("enableContentProtectionReceiver: ");
pw.println(mDevCfgEnableContentProtectionReceiver);
pw.print(prefix2);
- pw.print("contentProtectionAppsBlocklistSize: ");
- pw.println(mDevCfgContentProtectionAppsBlocklistSize);
- pw.print(prefix2);
pw.print("contentProtectionBufferSize: ");
pw.println(mDevCfgContentProtectionBufferSize);
pw.print(prefix2);
@@ -877,9 +856,8 @@
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- return new ContentProtectionBlocklistManager(
- new ContentProtectionPackageManager(getContext()));
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ return new ContentProtectionAllowlistManager();
}
/** @hide */
@@ -1429,7 +1407,7 @@
private boolean isContentProtectionReceiverEnabled(
@UserIdInt int userId, @NonNull String packageName) {
if (mContentProtectionServiceComponentName == null
- || mContentProtectionBlocklistManager == null
+ || mContentProtectionAllowlistManager == null
|| mContentProtectionConsentManager == null) {
return false;
}
@@ -1443,7 +1421,7 @@
}
}
return mContentProtectionConsentManager.isConsentGranted(userId)
- && mContentProtectionBlocklistManager.isAllowed(packageName);
+ && mContentProtectionAllowlistManager.isAllowed(packageName);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
new file mode 100644
index 0000000..59af526
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
@@ -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.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+/**
+ * Manages whether the content protection is enabled for an app using a allowlist.
+ *
+ * @hide
+ */
+public class ContentProtectionAllowlistManager {
+
+ private static final String TAG = "ContentProtectionAllowlistManager";
+
+ public ContentProtectionAllowlistManager() {}
+
+ /** Returns true if the package is allowed. */
+ public boolean isAllowed(@NonNull String packageName) {
+ Slog.v(TAG, packageName);
+ return false;
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
deleted file mode 100644
index a0fd28b..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Manages whether the content protection is enabled for an app using a blocklist.
- *
- * @hide
- */
-public class ContentProtectionBlocklistManager {
-
- private static final String TAG = "ContentProtectionBlocklistManager";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Nullable private Set<String> mPackageNameBlocklist;
-
- public ContentProtectionBlocklistManager(
- @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
- mContentProtectionPackageManager = contentProtectionPackageManager;
- }
-
- public boolean isAllowed(@NonNull String packageName) {
- if (mPackageNameBlocklist == null) {
- // List not loaded or failed to load, don't run on anything
- return false;
- }
- if (mPackageNameBlocklist.contains(packageName)) {
- return false;
- }
- PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
- if (packageInfo == null) {
- return false;
- }
- if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
- return false;
- }
- return true;
- }
-
- public void updateBlocklist(int blocklistSize) {
- Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
- mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
- }
-
- @Nullable
- private Set<String> readPackageNameBlocklist(int blocklistSize) {
- if (blocklistSize <= 0) {
- // Explicitly requested an empty blocklist
- return Collections.emptySet();
- }
- List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
- if (lines == null) {
- return null;
- }
- return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
- }
-
- @VisibleForTesting
- @Nullable
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- try (FileReader fileReader = new FileReader(filename);
- BufferedReader bufferedReader = new BufferedReader(fileReader)) {
- return bufferedReader
- .lines()
- .map(line -> line.trim())
- .filter(line -> !line.isBlank())
- .collect(Collectors.toList());
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to read: " + filename, ex);
- return null;
- }
- }
-}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
deleted file mode 100644
index 4ebac07..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.util.Slog;
-
-import java.util.Arrays;
-
-/**
- * Basic package manager for content protection using content capture.
- *
- * @hide
- */
-public class ContentProtectionPackageManager {
-
- private static final String TAG = "ContentProtectionPackageManager";
-
- private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
- PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
-
- @NonNull private final PackageManager mPackageManager;
-
- public ContentProtectionPackageManager(@NonNull Context context) {
- mPackageManager = context.getPackageManager();
- }
-
- @Nullable
- public PackageInfo getPackageInfo(@NonNull String packageName) {
- try {
- return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
- } catch (NameNotFoundException ex) {
- Slog.w(TAG, "Package info not found for: " + packageName, ex);
- return null;
- }
- }
-
- public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null
- && isUpdatedSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
- public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
- return packageInfo.requestedPermissions != null
- && Arrays.asList(packageInfo.requestedPermissions)
- .contains(Manifest.permission.INTERNET);
- }
-}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b696998..3985a0e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -136,7 +136,6 @@
":display-device-config",
":display-layout-config",
":device-state-config",
- ":framework-core-nfc-infcadapter-sources",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
@@ -149,12 +148,14 @@
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V2-java",
+ "android.nfc.flags-aconfig-java",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
"service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
+ "device_policy_aconfig_flags_lib",
],
plugins: ["ImmutabilityAnnotationProcessor"],
@@ -196,8 +197,8 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
- "android.nfc.flags-aconfig-java",
"cbor-java",
+ "dropbox_flags_lib",
"icu4j_calendar_astronomer",
"android.security.aaid_aidl-java",
"netd-client",
@@ -206,6 +207,7 @@
"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/BrickReceiver.java b/services/core/java/com/android/server/BrickReceiver.java
deleted file mode 100644
index cff3805..0000000
--- a/services/core/java/com/android/server/BrickReceiver.java
+++ /dev/null
@@ -1,31 +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 com.android.server;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.BroadcastReceiver;
-import android.os.SystemService;
-import android.util.Slog;
-
-public class BrickReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!");
- SystemService.start("brick");
- }
-}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 55069b7..f82a6aa 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.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.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 @@
* 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 @@
// 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 @@
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 @@
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 8624dd5..15fc2dc 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -386,15 +386,14 @@
private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
/**
- * mLocalUnlockedUsers affects the return value of isUserUnlocked. If
- * any value in the array changes, then the binder cache for
- * isUserUnlocked must be invalidated. When adding mutating methods to
- * WatchedLockedUsers, be sure to invalidate the cache in the new
- * methods.
+ * mCeUnlockedUsers affects the return value of {@link UserManager#isUserUnlocked}. If any
+ * value in the array changes, then the binder cache for {@link UserManager#isUserUnlocked} must
+ * be invalidated. When adding mutating methods to this class, be sure to invalidate the cache
+ * in the new methods.
*/
- private static class WatchedLockedUsers {
+ private static class WatchedUnlockedUsers {
private int[] users = EmptyArray.INT;
- public WatchedLockedUsers() {
+ public WatchedUnlockedUsers() {
invalidateIsUserUnlockedCache();
}
public void append(int userId) {
@@ -426,10 +425,14 @@
}
}
- /** Set of users that we know are unlocked. */
+ /** Set of users whose CE storage is unlocked. */
@GuardedBy("mLock")
- private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
- /** Set of users that system knows are unlocked. */
+ private WatchedUnlockedUsers mCeUnlockedUsers = new WatchedUnlockedUsers();
+
+ /**
+ * Set of users that are in the RUNNING_UNLOCKED state. This differs from {@link
+ * mCeUnlockedUsers} in that a user can be stopped but still have its CE storage unlocked.
+ */
@GuardedBy("mLock")
private int[] mSystemUnlockedUsers = EmptyArray.INT;
@@ -1144,11 +1147,10 @@
}
}
- // If vold knows that some users have their storage unlocked already (which
- // can happen after a "userspace reboot"), then add those users to
- // mLocalUnlockedUsers. Do this right away and don't wait until
- // PHASE_BOOT_COMPLETED, since the system may unlock users before then.
- private void restoreLocalUnlockedUsers() {
+ // If vold knows that some users have their CE storage unlocked already (which can happen after
+ // a "userspace reboot"), then add those users to mCeUnlockedUsers. Do this right away and
+ // don't wait until PHASE_BOOT_COMPLETED, since the system may unlock users before then.
+ private void restoreCeUnlockedUsers() {
final int[] userIds;
try {
userIds = mVold.getUnlockedUsers();
@@ -1164,7 +1166,7 @@
// reconnecting to vold after it crashed and was restarted, in
// which case things will be the other way around --- we'll know
// about the unlocked users but vold won't.
- mLocalUnlockedUsers.appendAll(userIds);
+ mCeUnlockedUsers.appendAll(userIds);
}
}
}
@@ -2075,7 +2077,7 @@
connectVold();
}, DateUtils.SECOND_IN_MILLIS);
} else {
- restoreLocalUnlockedUsers();
+ restoreCeUnlockedUsers();
onDaemonConnected();
}
}
@@ -3005,7 +3007,7 @@
// 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;
@@ -3229,15 +3231,15 @@
@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);
- // New keys are always unlocked.
+ mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+ // Since the user's CE key was just created, the user's CE storage is now unlocked.
synchronized (mLock) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3246,15 +3248,15 @@
@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);
- // Destroying a key also locks it.
+ mVold.destroyUserStorageKeys(userId);
+ // Since the user's CE key was just destroyed, the user's CE storage is now locked.
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3264,60 +3266,60 @@
/* 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) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
}
@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;
}
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
}
@Override
- public boolean isUserKeyUnlocked(int userId) {
+ public boolean isCeStorageUnlocked(int userId) {
synchronized (mLock) {
- return mLocalUnlockedUsers.contains(userId);
+ return mCeUnlockedUsers.contains(userId);
}
}
@@ -3717,8 +3719,8 @@
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);
}
@@ -3844,15 +3846,15 @@
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);
}
@@ -3912,7 +3914,7 @@
} 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) {
@@ -4716,7 +4718,7 @@
}
pw.println();
- pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+ pw.println("CE unlocked users: " + mCeUnlockedUsers);
pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1650a96..bdda95e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@
final MessageHandler mHandler;
+ private static final int TIMEOUT_DELAY_MS = 1000 * 60;
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -4942,6 +4943,7 @@
synchronized (mSessions) {
mSessions.put(toString(), this);
}
+ scheduleTimeout();
if (response != null) {
try {
response.asBinder().linkToDeath(this, 0 /* flags */);
@@ -5109,6 +5111,11 @@
}
}
+ private void scheduleTimeout() {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+ }
+
public void cancelTimeout() {
mHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
@@ -5146,6 +5153,9 @@
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onTimedOut");
+ }
if (response != null) {
try {
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 31817f1..e88d0c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -77,8 +77,10 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -2297,7 +2299,7 @@
return;
}
// TODO(b/148767783): should we check all profiles under user0?
- UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
BackgroundThread.getExecutor());
}
@@ -4646,7 +4648,7 @@
// 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);
@@ -4840,6 +4842,7 @@
if (!mConstants.mEnableWaitForFinishAttachApplication) {
finishAttachApplicationInner(startSeq, callingUid, pid);
}
+ maybeSendBootCompletedLocked(app);
} catch (Exception e) {
// We need kill the process group here. (b/148588589)
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
@@ -5066,6 +5069,45 @@
}
}
+ /**
+ * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+ */
+ private void maybeSendBootCompletedLocked(ProcessRecord app) {
+ // Nothing to do if it wasn't previously stopped
+ if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+
+ // Send LOCKED_BOOT_COMPLETED, if necessary
+ if (app.getApplicationInfo().isEncryptionAware()) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED),
+ REASON_LOCKED_BOOT_COMPLETED);
+ }
+ // Send BOOT_COMPLETED if the user is unlocked
+ if (StorageManager.isUserKeyUnlocked(app.userId)) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
+ REASON_BOOT_COMPLETED);
+ }
+ app.setWasForceStopped(false);
+ }
+
+ /** Send a boot_completed broadcast to app */
+ private void sendBootBroadcastToAppLocked(ProcessRecord app, Intent intent,
+ @PowerExemptionManager.ReasonCode int reason) {
+ intent.setPackage(app.info.packageName);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, app.userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
+ reason);
+
+ broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ null, null, AppOpsManager.OP_NONE,
+ bOptions.toBundle(), true,
+ false, MY_PID, SYSTEM_UID,
+ SYSTEM_UID, MY_PID, app.userId);
+ }
+
@Override
public void showBootMessage(final CharSequence msg, final boolean always) {
if (Binder.getCallingUid() != myUid()) {
@@ -5476,7 +5518,7 @@
+ " Calling package: " + packageName + "; intent: " + intent
+ "; options: " + options);
}
- target.send(code, intent, resolvedType, allowlistToken, null,
+ target.send(code, intent, resolvedType, null, null,
requiredPermission, options);
} catch (RemoteException e) {
}
@@ -9676,9 +9718,29 @@
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 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);
}
@@ -9687,6 +9749,14 @@
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);
}
@@ -9700,6 +9770,7 @@
}
final int callingUid = Binder.getCallingUid();
+ mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
}
@Override
@@ -10001,6 +10072,8 @@
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
+ mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ pw.println("-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
if (dumpPackage == null) {
@@ -10397,6 +10470,12 @@
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];
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a057f32..69bf612 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 @@
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 @@
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 @@
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 @@
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 0000000..edca74f
--- /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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0ab81a5..9e48b0a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -121,6 +121,7 @@
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
+import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor;
import com.android.server.power.stats.PowerStatsAggregator;
import com.android.server.power.stats.PowerStatsScheduler;
import com.android.server.power.stats.PowerStatsStore;
@@ -440,7 +441,9 @@
.trackUidStates(
AggregatedPowerStatsConfig.STATE_POWER,
AggregatedPowerStatsConfig.STATE_SCREEN,
- AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(
+ new CpuAggregatedPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
return config;
}
diff --git a/services/core/java/com/android/server/am/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
index 8f79133..016d3cd 100644
--- a/services/core/java/com/android/server/am/LowMemDetector.java
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -23,6 +23,7 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING;
import android.annotation.IntDef;
+import android.os.Trace;
import com.android.internal.annotations.GuardedBy;
@@ -90,17 +91,31 @@
private native int waitForPressure();
private final class LowMemThread extends Thread {
+ private boolean mIsTracingMemCriticalLow;
+
+ LowMemThread() {
+ super("LowMemThread");
+ }
+
public void run() {
while (true) {
// sleep waiting for a PSI event
int newPressureState = waitForPressure();
+ // PSI event detected
+ boolean isCriticalLowMemory = newPressureState == ADJ_MEM_FACTOR_CRITICAL;
+ if (isCriticalLowMemory && !mIsTracingMemCriticalLow) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "criticalLowMemory");
+ } else if (!isCriticalLowMemory && mIsTracingMemCriticalLow) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ mIsTracingMemCriticalLow = isCriticalLowMemory;
if (newPressureState == -1) {
// epoll broke, tear this down
mAvailable = false;
break;
}
- // got a PSI event? let's update lowmem info
+ // got an actual PSI event? let's update lowmem info
synchronized (mPressureStateLock) {
mPressureState = newPressureState;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 59d8e7e..614caffe 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 @@
@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 @@
mSystemServerSocketForZygote.getFileDescriptor(),
EVENT_INPUT, this::handleZygoteMessages);
}
+ mAppStartInfoTracker.init(mService);
mAppExitInfoTracker.init(mService);
mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper());
}
}
void onSystemReady() {
+ mAppStartInfoTracker.onSystemReady();
mAppExitInfoTracker.onSystemReady();
}
@@ -3257,6 +3263,17 @@
hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
+ // Check if we should mark the processrecord for first launch after force-stopping
+ if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+ .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+ // If the package was launched in the past but is currently stopped, only then it
+ // should be considered as stopped after use. Do not mark it if it's the first launch.
+ if (wasPackageEverLaunched) {
+ r.setWasForceStopped(true);
+ }
+ }
+
if (!isolated && !isSdkSandbox
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 940c58b..354f3d3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,6 +23,7 @@
import android.app.ProcessMemoryState.HostingComponentType;
import android.content.pm.ApplicationInfo;
import android.os.Debug;
+import android.os.Process;
import android.os.SystemClock;
import android.util.DebugUtils;
import android.util.TimeUtils;
@@ -271,15 +272,17 @@
origBase.makeInactive();
}
final ApplicationInfo info = mApp.info;
+ final int attributionUid = getUidForAttribution(mApp);
final ProcessState baseProcessTracker = tracker.getProcessStateLocked(
- info.packageName, info.uid, info.longVersionCode, mApp.processName);
+ info.packageName, attributionUid, info.longVersionCode,
+ mApp.processName);
setBaseProcessTracker(baseProcessTracker);
baseProcessTracker.makeActive();
pkgList.forEachPackage((pkgName, holder) -> {
if (holder.state != null && holder.state != origBase) {
holder.state.makeInactive();
}
- tracker.updateProcessStateHolderLocked(holder, pkgName, mApp.info.uid,
+ tracker.updateProcessStateHolderLocked(holder, pkgName, attributionUid,
mApp.info.longVersionCode, mApp.processName);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -536,7 +539,7 @@
tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss);
pkgList.forEachPackageProcessStats(holder ->
FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
- mApp.info.uid,
+ getUidForAttribution(mApp),
holder.state.getName(),
holder.state.getPackage(),
mLastCachedPss,
@@ -595,6 +598,21 @@
tracker.mPendingMemState = -1;
}
+ /**
+ * Returns the uid that should be used for attribution purposes in profiling / stats.
+ *
+ * In most cases this returns the uid of the process itself. For isolated processes though,
+ * since the process uid is dynamically allocated and can't easily be traced back to the app,
+ * for attribution we use the app package uid.
+ */
+ private static int getUidForAttribution(ProcessRecord processRecord) {
+ if (Process.isIsolatedUid(processRecord.uid)) {
+ return processRecord.info.uid;
+ } else {
+ return processRecord.uid;
+ }
+ }
+
@GuardedBy("mProfilerLock")
int getPid() {
return mPid;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f02b8c7..2c6e598 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -438,6 +438,9 @@
final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+ /** Whether the app was launched from a stopped state and is being unstopped. */
+ volatile boolean mWasForceStopped;
+
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
long startUptime, long startElapsedTime) {
this.mStartUid = startUid;
@@ -1602,4 +1605,12 @@
List<ProcessRecord> getLruProcessList() {
return mService.mProcessList.getLruProcessesLOSP();
}
+
+ public void setWasForceStopped(boolean stopped) {
+ mWasForceStopped = stopped;
+ }
+
+ public boolean wasForceStopped() {
+ return mWasForceStopped;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index a165e88..f5f2b10 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -347,8 +347,10 @@
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.binding.service.app.mServices != this
- && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+
+ final boolean isSameProcess = cr.binding.service.app != null
+ && cr.binding.service.app.mServices == this;
+ if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index aa788eb..439fe0b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -123,6 +123,7 @@
"angle",
"arc_next",
"bluetooth",
+ "build",
"biometrics_framework",
"biometrics_integration",
"camera_platform",
@@ -136,24 +137,30 @@
"context_hub",
"core_experiments_team_internal",
"core_graphics",
+ "game",
"haptics",
"hardware_backed_security_mainline",
"input",
"machine_learning",
+ "mainline_modularization",
"mainline_sdk",
"media_audio",
"media_drm",
"media_solutions",
"nfc",
+ "pdf_viewer",
"pixel_audio_android",
"pixel_system_sw_touch",
"pixel_watch",
"platform_security",
"power",
"preload_safety",
+ "privacy_infra_policy",
+ "resource_manager",
"responsible_apis",
"rust",
"safety_center",
+ "sensors",
"system_performance",
"test_suites",
"text",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index de4ad20..728bace 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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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;
}
@@ -3387,7 +3387,7 @@
}
- private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+ BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@PowerWhitelistManager.ReasonCode int reasonCode) {
long duration = 10_000;
final ActivityManagerInternal amInternal =
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 292fc14..51cb950 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 @@
@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 eea3d38..2336753 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 @@
}
/*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 @@
}
/*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 @@
}
/*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 @@
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 @@
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 e08fdd6..d707689 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 @@
}
/**
+ * 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
@@ -904,9 +924,6 @@
@NonNull List<AudioDeviceAttributes> devices) {
int status = AudioSystem.ERROR;
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setPreferredDevicesForStrategy, strategy: " + strategy
- + " devices: " + devices)).printLog(TAG));
status = setDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */);
}
@@ -932,10 +949,6 @@
int status = AudioSystem.ERROR;
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "removePreferredDevicesForStrategy, strategy: "
- + strategy)).printLog(TAG));
-
status = clearDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */);
}
@@ -954,10 +967,6 @@
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
List<AudioDeviceAttributes> devices = new ArrayList<>();
devices.add(device);
-
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
- + " device: " + device)).printLog(TAG));
status = addDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
}
@@ -975,11 +984,6 @@
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
List<AudioDeviceAttributes> devices = new ArrayList<>();
devices.add(device);
-
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "removeDeviceAsNonDefaultForStrategyAndSave, strategy: "
- + strategy + " devices: " + device)).printLog(TAG));
-
status = removeDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
}
@@ -992,8 +996,8 @@
/*package*/ void registerStrategyPreferredDevicesDispatcher(
- @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
- mPrefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mPrefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1002,8 +1006,8 @@
}
/*package*/ void registerStrategyNonDefaultDevicesDispatcher(
- @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
- mNonDefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mNonDefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1084,8 +1088,8 @@
}
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
- @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
- mDevRoleCapturePresetDispatchers.register(dispatcher);
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+ mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1414,6 +1418,8 @@
updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
if (!connect) {
purgeDevicesRoles_l();
+ } else {
+ addAudioDeviceInInventoryIfNeeded(attributes);
}
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1702,6 +1708,7 @@
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2006,9 +2013,9 @@
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 +2025,7 @@
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 +2136,7 @@
sensorUuid));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2462,6 +2471,9 @@
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 +2487,9 @@
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 +2503,9 @@
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 3243385..a8fa313 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.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.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.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 @@
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 @@
private final SoundDoseHelper mSoundDoseHelper;
+ private final HardeningEnforcer mHardeningEnforcer;
+
private final Object mSupportedSystemUsagesLock = new Object();
@GuardedBy("mSupportedSystemUsagesLock")
private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -1312,6 +1320,8 @@
mDisplayManager = context.getSystemService(DisplayManager.class);
mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
+
+ mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive());
}
private void initVolumeStreamStates() {
@@ -1381,7 +1391,6 @@
// check on volume initialization
checkVolumeRangeInitialization("AudioService()");
-
}
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1394,6 +1403,14 @@
}
};
+ 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 @@
// IPC methods
///////////////////////////////////////////////////////////////////////////
/**
- * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+ * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes)
* @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
* List<AudioDeviceAttributes>)
*/
@@ -2891,8 +2908,11 @@
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));
@@ -2916,11 +2936,11 @@
super.removePreferredDevicesForStrategy_enforcePermission();
final String logString =
- String.format("removePreferredDeviceForStrategy strat:%d", strategy);
+ String.format("removePreferredDevicesForStrategy strat:%d", strategy);
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
- if (status != AudioSystem.SUCCESS) {
+ if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
Log.e(TAG, String.format("Error %d in %s)", status, logString));
}
return status;
@@ -2948,7 +2968,7 @@
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -2963,6 +2983,9 @@
@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 @@
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));
@@ -2998,7 +3024,7 @@
}
final int status = mDeviceBroker.removeDeviceAsNonDefaultForStrategySync(strategy, device);
- if (status != AudioSystem.SUCCESS) {
+ if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
Log.e(TAG, String.format("Error %d in %s)", status, logString));
}
return status;
@@ -3023,7 +3049,7 @@
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -3036,7 +3062,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3060,7 +3087,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3076,7 +3104,7 @@
}
/**
- * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+ * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
*/
public int setPreferredDevicesForCapturePreset(
int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3095,6 +3123,8 @@
return AudioSystem.ERROR;
}
+ devices = retrieveBluetoothAddresses(devices);
+
final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
capturePreset, devices);
if (status != AudioSystem.SUCCESS) {
@@ -3114,7 +3144,7 @@
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
- if (status != AudioSystem.SUCCESS) {
+ if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) {
Log.e(TAG, String.format("Error %d in %s", status, logString));
}
return status;
@@ -3141,7 +3171,7 @@
status, capturePreset));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -3155,7 +3185,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/**
@@ -3175,7 +3206,9 @@
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 @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 @@
* 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 @@
* 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 @@
/** @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 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 @@
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 @@
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 @@
// verify parameters
Objects.requireNonNull(device);
+ device = retrieveBluetoothAddress(device);
+
return getDeviceVolumeBehaviorInt(device);
}
@@ -7547,9 +7627,12 @@
/**
* 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 @@
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 @@
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 @@
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 @@
+ 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 @@
super.getMutingExpectedDevice_enforcePermission();
synchronized (mMuteAwaitConnectionLock) {
- return mMutingExpectedDevice;
+ return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
}
}
@@ -10510,6 +10703,9 @@
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 @@
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 @@
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 @@
super.registerMuteAwaitConnectionDispatcher_enforcePermission();
if (register) {
- mMuteAwaitConnectionDispatchers.register(cb);
+ mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
} else {
mMuteAwaitConnectionDispatchers.unregister(cb);
}
@@ -10568,8 +10770,14 @@
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 @@
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
}
- dispatchMuteAwaitConnection(cb -> { try {
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
timedOutDevice, mutedUsages);
@@ -10597,13 +10806,14 @@
}
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 @@
@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 @@
@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 @@
@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 6af409e..7d7e6d0 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 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 @@
// Bluetooth headset device
private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
+ private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices =
+ new HashMap<>();
private @Nullable BluetoothHearingAid mHearingAid;
@@ -590,7 +594,16 @@
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 @@
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 @@
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 0000000..c7556da
--- /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 1760bb3..4538cad 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.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 @@
}
@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 9569f23..1898b80 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.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.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 @@
@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 @@
}
}
- 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 @@
@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 @@
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 @@
if (finished) {
Slog.d(TAG, "handleOnError: AuthSession finished");
mAuthSession = null;
- notifyAuthSessionChanged();
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
@@ -1244,7 +1186,6 @@
session.onDialogDismissed(reason, credentialAttestation);
mAuthSession = null;
- notifyAuthSessionChanged();
}
private void handleOnTryAgainPressed(long requestId) {
@@ -1294,7 +1235,6 @@
final boolean finished = session.onClientDied();
if (finished) {
mAuthSession = null;
- notifyAuthSessionChanged();
}
}
@@ -1409,16 +1349,6 @@
});
}
- 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 @@
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
- notifyAuthSessionChanged();
}
private void handleCancelAuthentication(long requestId) {
@@ -1471,7 +1400,6 @@
if (finished) {
Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
mAuthSession = null;
- notifyAuthSessionChanged();
}
}
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index c29d9bd..f085647 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -108,7 +108,7 @@
final boolean credentialRequested = Utils.isCredentialRequested(promptInfo);
final boolean credentialAvailable = trustManager.isDeviceSecure(userId,
- context.getAssociatedDisplayId());
+ context.getDeviceId());
// Assuming that biometric authenticators are listed in priority-order, the rest of this
// function will attempt to find the first authenticator that's as strong or stronger than
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 76dde54..e3c0cf7 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -646,7 +646,7 @@
intendingUid,
intendingUserId,
intendingDeviceId)
- || isDeviceLocked(intendingUserId)) {
+ || isDeviceLocked(intendingUserId, deviceId)) {
return null;
}
synchronized (mLock) {
@@ -686,7 +686,7 @@
intendingUserId,
intendingDeviceId,
false)
- || isDeviceLocked(intendingUserId)) {
+ || isDeviceLocked(intendingUserId, deviceId)) {
return null;
}
synchronized (mLock) {
@@ -710,7 +710,7 @@
intendingUserId,
intendingDeviceId,
false)
- || isDeviceLocked(intendingUserId)) {
+ || isDeviceLocked(intendingUserId, deviceId)) {
return false;
}
synchronized (mLock) {
@@ -785,7 +785,7 @@
intendingUserId,
intendingDeviceId,
false)
- || isDeviceLocked(intendingUserId)) {
+ || isDeviceLocked(intendingUserId, deviceId)) {
return false;
}
synchronized (mLock) {
@@ -814,7 +814,7 @@
intendingUserId,
intendingDeviceId,
false)
- || isDeviceLocked(intendingUserId)) {
+ || isDeviceLocked(intendingUserId, deviceId)) {
return null;
}
synchronized (mLock) {
@@ -1150,12 +1150,12 @@
&& text.equals(clipboard.primaryClip.getItemAt(0).getText());
}
- private boolean isDeviceLocked(@UserIdInt int userId) {
+ private boolean isDeviceLocked(@UserIdInt int userId, int deviceId) {
final long token = Binder.clearCallingIdentity();
try {
final KeyguardManager keyguardManager = getContext().getSystemService(
KeyguardManager.class);
- return keyguardManager != null && keyguardManager.isDeviceLocked(userId);
+ return keyguardManager != null && keyguardManager.isDeviceLocked(userId, deviceId);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS
index b80f340..6ca7803 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 64b17e5..84be521 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 @@
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 3581981..58a654a 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.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 @@
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 59844e1..bba5ba3 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.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 @@
@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 d550650..ac5dd20 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.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 @@
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 0d6635d..3de188f 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 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 @@
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/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 4f1df3f..70d4ad2 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,14 +120,14 @@
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return createMode(width, height, refreshRate, new float[0], new int[0]);
+ return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
- public static Display.Mode createMode(int width, int height, float refreshRate,
+ public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates,
@Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- alternativeRefreshRates, supportedHdrTypes);
+ vsyncRate, alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index cfbe0c6..a0beedb 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.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 @@
*/
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 ea52a3d..67e612d 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 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 @@
*/
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 e5f01df..087cf20 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.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 @@
*/
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.
@@ -586,8 +590,7 @@
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
- // TODO: b/306170135 - return TextUtils package name check instead
- mExtraDisplayEventLogging = true;
+ mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
}
public void setupSchedulerPolicies() {
@@ -1132,6 +1135,7 @@
new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
overriddenInfo.refreshRateOverride,
+ currentMode.getVsyncRate(),
new float[0], currentMode.getSupportedHdrTypes());
overriddenInfo.modeId =
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
@@ -3043,8 +3047,7 @@
}
private boolean extraLogging(String packageName) {
- // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
- return true;
+ return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
// Runs on Handler thread.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ce98559..915f5db 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.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 @@
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 @@
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 0f00027..fc596dc 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.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 @@
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 @@
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 2c257a1..be03a80 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.view.Choreographer;
import android.view.Display;
+import com.android.server.display.utils.DebugUtils;
+
import java.io.PrintWriter;
/**
@@ -48,7 +50,9 @@
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 39172b8..a9f78fd 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.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 @@
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 3c522e7..0521b8a 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 @@
import android.util.Slog;
+import com.android.server.display.utils.DebugUtils;
+
import java.io.PrintWriter;
import java.util.Arrays;
@@ -27,7 +29,9 @@
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/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index e5d38cb..be3207d 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -319,10 +319,10 @@
SurfaceControl.DisplayMode other = displayModes[j];
boolean isAlternative = j != i && other.width == mode.width
&& other.height == mode.height
- && other.refreshRate != mode.refreshRate
+ && other.peakRefreshRate != mode.peakRefreshRate
&& other.group == mode.group;
if (isAlternative) {
- alternativeRefreshRates.add(displayModes[j].refreshRate);
+ alternativeRefreshRates.add(displayModes[j].peakRefreshRate);
}
}
Collections.sort(alternativeRefreshRates);
@@ -1360,7 +1360,7 @@
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
- mMode = createMode(mode.width, mode.height, mode.refreshRate,
+ mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate,
alternativeRefreshRates, mode.supportedHdrTypes);
}
@@ -1375,7 +1375,9 @@
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
- == Float.floatToIntBits(mode.refreshRate);
+ == Float.floatToIntBits(mode.peakRefreshRate)
+ && Float.floatToIntBits(mMode.getVsyncRate())
+ == Float.floatToIntBits(mode.vsyncRate);
}
public String toString() {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index c55bc62..f3425d2e 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.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 @@
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 cd3a453..3fd58e8 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.widget.TextView;
import com.android.internal.util.DumpUtils;
+import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
@@ -47,8 +48,10 @@
*/
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 1e13974..7bc7971 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.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 @@
@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 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 e3d38e7..7660cf8 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 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 @@
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 955b8d9..873598a 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.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 @@
*/
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 d953e8e..7f3ea6a 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.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 @@
* 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 0000000..1496495
--- /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 0000000..067288d
--- /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 0000000..fee4bf3
--- /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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3435e56..0c4ecbc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5529,6 +5529,42 @@
return canAccess;
}
+ @GuardedBy("ImfLock.class")
+ private void switchKeyboardLayoutLocked(int direction) {
+ final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+ if (currentImi == null) {
+ return;
+ }
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+ final InputMethodSubtypeHandle nextSubtypeHandle =
+ mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+ direction > 0);
+ if (nextSubtypeHandle == null) {
+ return;
+ }
+ final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+ if (nextImi == null) {
+ return;
+ }
+
+ final int subtypeCount = nextImi.getSubtypeCount();
+ if (subtypeCount == 0) {
+ if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+ }
+ return;
+ }
+
+ for (int i = 0; i < subtypeCount; ++i) {
+ if (nextSubtypeHandle.equals(
+ InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+ setInputMethodLocked(nextImi.getId(), i);
+ return;
+ }
+ }
+ }
+
private void publishLocalService() {
LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
}
@@ -5734,38 +5770,7 @@
@Override
public void switchKeyboardLayout(int direction) {
synchronized (ImfLock.class) {
- final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
- if (currentImi == null) {
- return;
- }
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
- final InputMethodSubtypeHandle nextSubtypeHandle =
- mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
- direction > 0);
- if (nextSubtypeHandle == null) {
- return;
- }
- final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
- if (nextImi == null) {
- return;
- }
-
- final int subtypeCount = nextImi.getSubtypeCount();
- if (subtypeCount == 0) {
- if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
- }
- return;
- }
-
- for (int i = 0; i < subtypeCount; ++i) {
- if (nextSubtypeHandle.equals(
- InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
- setInputMethodLocked(nextImi.getId(), i);
- return;
- }
- }
+ switchKeyboardLayoutLocked(direction);
}
}
@@ -6767,5 +6772,21 @@
public void resetStylusHandwriting(int requestId) {
mImms.resetStylusHandwriting(requestId);
}
+
+ @BinderThread
+ @Override
+ public void switchKeyboardLayoutAsync(int direction) {
+ synchronized (ImfLock.class) {
+ if (!mImms.calledWithValidTokenLocked(mToken)) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.switchKeyboardLayoutLocked(direction);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 74b7f08..323cdc5 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -474,13 +474,14 @@
// If we have a GNSS provider override, add the hardware provider as a standalone
// option for use by apps with the correct permission. Note the GNSS HAL can only
// support a single client, so mGnssManagerService.getGnssLocationProvider() can
- // only be installed with a single provider.
+ // only be installed with a single provider. Locations from this provider won't
+ // be reported through the passive provider.
LocationProviderManager gnssHardwareManager =
new LocationProviderManager(
mContext,
mInjector,
GPS_HARDWARE_PROVIDER,
- mPassiveManager,
+ /*passiveManager=*/ null,
Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
addLocationProviderManager(
gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f35b045..568618e 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 @@
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 @@
// 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 @@
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 @@
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 @@
}
}
- 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 @@
* 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 @@
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 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 @@
return;
}
onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
- unlockUserKey(userId, result.syntheticPassword);
+ unlockCeStorage(userId, result.syntheticPassword);
}
}
@@ -2775,7 +2777,7 @@
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 @@
unlockKeystore(userId, sp);
- unlockUserKey(userId, sp);
+ unlockCeStorage(userId, sp);
unlockUser(userId);
@@ -2900,7 +2902,7 @@
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/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 67a1ccd..78077a8 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -40,6 +40,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.util.List;
import java.util.Objects;
@@ -358,21 +359,8 @@
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
- if (mPendingSessionCreationRequest != null) {
- SessionCreationRequest sessionCreationRequest;
- synchronized (mRequestLock) {
- sessionCreationRequest = mPendingSessionCreationRequest;
- mPendingSessionCreationRequest = null;
- }
- if (sessionCreationRequest != null) {
- if (TextUtils.equals(mSelectedRouteId, sessionCreationRequest.mRouteId)) {
- mCallback.onSessionCreated(this,
- sessionCreationRequest.mRequestId, newSessionInfo);
- } else {
- mCallback.onRequestFailed(this, sessionCreationRequest.mRequestId,
- MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
- }
- }
+ synchronized (mRequestLock) {
+ reportPendingSessionRequestResultLockedIfNeeded(newSessionInfo);
}
if (Objects.equals(oldSessionInfo, newSessionInfo)) {
@@ -395,6 +383,59 @@
}
}
+ @GuardedBy("mRequestLock")
+ private void reportPendingSessionRequestResultLockedIfNeeded(
+ RoutingSessionInfo newSessionInfo) {
+ if (mPendingSessionCreationRequest == null) {
+ // No pending request, nothing to report.
+ return;
+ }
+
+ long pendingRequestId = mPendingSessionCreationRequest.mRequestId;
+ if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) {
+ if (DEBUG) {
+ Slog.w(
+ TAG,
+ "Session creation success to route "
+ + mPendingSessionCreationRequest.mRouteId);
+ }
+ mPendingSessionCreationRequest = null;
+ mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo);
+ } else {
+ boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute();
+ if (!Flags.enableWaitingStateForSystemSessionCreationRequest()
+ || !isRequestedRouteConnectedBtRoute) {
+ if (DEBUG) {
+ Slog.w(
+ TAG,
+ "Session creation failed to route "
+ + mPendingSessionCreationRequest.mRouteId);
+ }
+ mPendingSessionCreationRequest = null;
+ mCallback.onRequestFailed(
+ this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
+ } else if (DEBUG) {
+ Slog.w(
+ TAG,
+ "Session creation waiting state to route "
+ + mPendingSessionCreationRequest.mRouteId);
+ }
+ }
+ }
+
+ @GuardedBy("mRequestLock")
+ private boolean isRequestedRouteConnectedBtRoute() {
+ // Using AllRoutes instead of TransferableRoutes as BT Stack sends an intermediate update
+ // where two BT routes are active so the transferable routes list is empty.
+ // See b/307723189 for context
+ for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) {
+ if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void publishProviderState() {
updateProviderState();
notifyProviderState();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index ce35a61..893ed61 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,7 +76,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -385,10 +384,12 @@
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
- mMediaProjectionMetricsLogger.notifyProjectionStateChange(
- mProjectionGrant.uid,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ if (incomingSession != null) {
+ // Only log in progress when session is not null.
+ // setContentRecordingSession is called with a null session for the stop case.
+ mMediaProjectionMetricsLogger.logInProgress(
+ mProjectionGrant.uid, incomingSession.getTargetUid());
+ }
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
@@ -463,6 +464,21 @@
mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource);
}
+ @VisibleForTesting
+ void notifyPermissionRequestDisplayed(int hostUid) {
+ mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid);
+ }
+
+ @VisibleForTesting
+ void notifyPermissionRequestCancelled(int hostUid) {
+ mMediaProjectionMetricsLogger.logProjectionPermissionRequestCancelled(hostUid);
+ }
+
+ @VisibleForTesting
+ void notifyAppSelectorDisplayed(int hostUid) {
+ mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid);
+ }
+
/**
* Handles result of dialog shown from
* {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -841,12 +857,12 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestStateChange(int hostUid, int state,
- int sessionCreationSource) {
- notifyPermissionRequestStateChange_enforcePermission();
+ public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+ notifyPermissionRequestInitiated_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
+ MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
+ hostUid, sessionCreationSource);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -854,12 +870,35 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
- notifyPermissionRequestInitiated_enforcePermission();
+ public void notifyPermissionRequestDisplayed(int hostUid) {
+ notifyPermissionRequestDisplayed_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
- hostUid, sessionCreationSource);
+ MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestCancelled(int hostUid) {
+ notifyPermissionRequestCancelled_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyAppSelectorDisplayed(int hostUid) {
+ notifyAppSelectorDisplayed_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
index c8b932a..d7fefeb 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -16,7 +16,15 @@
package com.android.server.media.projection;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+
import android.content.Context;
+import android.util.Log;
import com.android.internal.util.FrameworkStatsLog;
@@ -24,6 +32,8 @@
/** Class for emitting logs describing a MediaProjection session. */
public class MediaProjectionMetricsLogger {
+ private static final String TAG = "MediaProjectionMetricsLogger";
+
private static final int TARGET_UID_UNKNOWN = -2;
private static final int TIME_SINCE_LAST_ACTIVE_UNKNOWN = -1;
@@ -75,6 +85,7 @@
* </ul>
*/
public void logInitiated(int hostUid, int sessionCreationSource) {
+ Log.d(TAG, "logInitiated");
Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession();
int timeSinceLastActiveInSeconds =
durationSinceLastActiveSession == null
@@ -82,43 +93,102 @@
: (int) durationSinceLastActiveSession.toSeconds();
write(
mSessionIdGenerator.createAndGetNewSessionId(),
- FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
hostUid,
TARGET_UID_UNKNOWN,
timeSinceLastActiveInSeconds,
sessionCreationSource);
}
- /** Logs that the capturing stopped, either normally or because of error. */
- public void logStopped(int hostUid, int targetUid) {
+ /**
+ * Logs that the user entered the setup flow and permission dialog is displayed. This state is
+ * not sent when the permission is already granted and we skipped showing the permission dialog.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logPermissionRequestDisplayed(int hostUid) {
+ Log.d(TAG, "logPermissionRequestDisplayed");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that requesting permission for media projection was cancelled by the user.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logProjectionPermissionRequestCancelled(int hostUid) {
write(
mSessionIdGenerator.getCurrentSessionId(),
FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+ .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED,
hostUid,
- targetUid,
+ TARGET_UID_UNKNOWN,
TIME_SINCE_LAST_ACTIVE_UNKNOWN,
FrameworkStatsLog
.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
- mTimestampStore.registerActiveSessionEnded();
}
- public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
- write(hostUid, state, sessionCreationSource);
+ /**
+ * Logs that the app selector dialog is shown for the user.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logAppSelectorDisplayed(int hostUid) {
+ Log.d(TAG, "logAppSelectorDisplayed");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
}
- private void write(int hostUid, int state, int sessionCreationSource) {
- mFrameworkStatsLogWrapper.write(
- /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
- /* session_id */ 123,
- /* state */ state,
- /* previous_state */ FrameworkStatsLog
- .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
- /* host_uid */ hostUid,
- /* target_uid */ -1,
- /* time_since_last_active */ 0,
- /* creation_source */ sessionCreationSource);
+ /**
+ * Logs that the virtual display is created and capturing the selected region begins.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logInProgress(int hostUid, int targetUid) {
+ Log.d(TAG, "logInProgress");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ hostUid,
+ targetUid,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the capturing stopped, either normally or because of error.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logStopped(int hostUid, int targetUid) {
+ boolean wasCaptureInProgress =
+ mPreviousState
+ == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+ Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress);
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+ hostUid,
+ targetUid,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+
+ if (wasCaptureInProgress) {
+ mTimestampStore.registerActiveSessionEnded();
+ }
}
private void write(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ca5699..4b5d52f 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.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 @@
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 @@
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 @@
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 @@
}
}
+ 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 72c4529..9f16662 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 0176989..8f5676b3 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 @@
}
}
+ /**
+ * 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/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1134714..e57ea0f 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.server.os;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -52,8 +54,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.HashSet;
import java.util.Objects;
import java.util.OptionalInt;
+import java.util.Set;
/**
* Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -101,10 +105,12 @@
private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
new ArrayMap<>();
+ @GuardedBy("mLock")
+ private final Set<String> mBugreportFilesToPersist = new HashSet<>();
+
/**
* Checks that a given file was generated on behalf of the given caller. If the file was
- * generated on behalf of the caller, it is removed from the bugreport mapping so that it
- * may not be retrieved again. If the file was not generated on behalf of the caller, an
+ * not generated on behalf of the caller, an
* {@link IllegalArgumentException} is thrown.
*
* @param callingInfo a (uid, package name) pair identifying the caller
@@ -114,35 +120,76 @@
* @throws IllegalArgumentException if {@code bugreportFile} is not associated with
* {@code callingInfo}.
*/
+ @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
+ conditional = true)
void ensureCallerPreviouslyGeneratedFile(
- Pair<Integer, String> callingInfo, String bugreportFile) {
+ Context context, Pair<Integer, String> callingInfo, int userId,
+ String bugreportFile) {
synchronized (mLock) {
- ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
- if (bugreportFilesForCaller != null
- && bugreportFilesForCaller.contains(bugreportFile)) {
- bugreportFilesForCaller.remove(bugreportFile);
- if (bugreportFilesForCaller.isEmpty()) {
- mBugreportFiles.remove(callingInfo);
+ if (onboardingBugreportV2Enabled()) {
+ final int uidForUser = Binder.withCleanCallingIdentity(() -> {
+ try {
+ return context.getPackageManager()
+ .getPackageUidAsUser(callingInfo.second, userId);
+ } catch (PackageManager.NameNotFoundException exception) {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
+ return -1;
+ }
+ });
+ if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ callingInfo.second + " does not hold the "
+ + "INTERACT_ACROSS_USERS permission to access "
+ + "cross-user bugreports.");
+ }
+ ArraySet<String> bugreportFilesForUid = mBugreportFiles.get(
+ new Pair<>(uidForUser, callingInfo.second));
+ if (bugreportFilesForUid == null
+ || !bugreportFilesForUid.contains(bugreportFile)) {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
}
} else {
- throw new IllegalArgumentException(
- "File " + bugreportFile + " was not generated"
- + " on behalf of calling package " + callingInfo.second);
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+ if (bugreportFilesForCaller != null
+ && bugreportFilesForCaller.contains(bugreportFile)) {
+ bugreportFilesForCaller.remove(bugreportFile);
+ if (bugreportFilesForCaller.isEmpty()) {
+ mBugreportFiles.remove(callingInfo);
+ }
+ } else {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
+
+ }
}
}
}
+ private static void throwInvalidBugreportFileForCallerException(
+ String bugreportFile, String packageName) {
+ throw new IllegalArgumentException("File " + bugreportFile + " was not generated on"
+ + " behalf of calling package " + packageName);
+ }
+
/**
* Associates a bugreport file with a caller, which is identified as a
* (uid, package name) pair.
*/
- void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+ void addBugreportFileForCaller(
+ Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) {
synchronized (mLock) {
if (!mBugreportFiles.containsKey(caller)) {
mBugreportFiles.put(caller, new ArraySet<>());
}
ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
bugreportFilesForCaller.add(bugreportFile);
+ if ((onboardingBugreportV2Enabled()) && keepOnRetrieval) {
+ mBugreportFilesToPersist.add(bugreportFile);
+ }
}
}
}
@@ -246,16 +293,17 @@
}
@Override
- @RequiresPermission(Manifest.permission.DUMP)
- public void retrieveBugreport(int callingUidUnused, String callingPackage,
- FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+ @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
+ public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
+ FileDescriptor bugreportFd, String bugreportFile,
+ boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- new Pair<>(callingUid, callingPackage), bugreportFile);
+ mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile);
} catch (IllegalArgumentException e) {
Slog.e(TAG, e.getMessage());
reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
@@ -281,10 +329,17 @@
// Wrap the listener so we can intercept binder events directly.
DumpstateListener myListener = new DumpstateListener(listener, ds,
new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+
+ boolean keepBugreportOnRetrieval = false;
+ if (onboardingBugreportV2Enabled()) {
+ keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains(
+ bugreportFile);
+ }
+
setCurrentDumpstateListenerLocked(myListener);
try {
- ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
- bugreportFile, myListener);
+ ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
+ bugreportFile, keepBugreportOnRetrieval, myListener);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in retrieveBugreport", e);
}
@@ -317,7 +372,8 @@
private void validateBugreportFlags(int flags) {
flags = clearBugreportFlag(flags,
BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
- | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
+ | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT
+ | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL);
if (flags != 0) {
Slog.w(TAG, "Unknown bugreport flags: " + flags);
throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -482,6 +538,9 @@
boolean reportFinishedFile =
(bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+ boolean keepBugreportOnRetrieval =
+ (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0;
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -490,7 +549,8 @@
}
DumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage), reportFinishedFile);
+ new Pair<>(callingUid, callingPackage), reportFinishedFile,
+ keepBugreportOnRetrieval);
setCurrentDumpstateListenerLocked(myListener);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
@@ -646,9 +706,16 @@
private final boolean mReportFinishedFile;
private int mProgress; // used for debugging purposes only
private boolean mDone;
+ private boolean mKeepBugreportOnRetrieval;
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
Pair<Integer, String> caller, boolean reportFinishedFile) {
+ this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
+ }
+
+ DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+ Pair<Integer, String> caller, boolean reportFinishedFile,
+ boolean keepBugreportOnRetrieval) {
if (DEBUG) {
Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
}
@@ -656,6 +723,7 @@
mDs = ds;
mCaller = caller;
mReportFinishedFile = reportFinishedFile;
+ mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -690,7 +758,8 @@
reportFinishedLocked("File: " + bugreportFile);
}
if (mReportFinishedFile) {
- mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ mBugreportFileManager.addBugreportFileForCaller(
+ mCaller, bugreportFile, mKeepBugreportOnRetrieval);
} else if (DEBUG) {
Slog.d(TAG, "Not reporting finished file");
}
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e53c436..ca149c5 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 @@
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/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index bd9be30..167c17c 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -121,7 +121,7 @@
StorageManagerInternal.class);
for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) {
final int flags;
- if (StorageManager.isUserKeyUnlocked(user.id)
+ if (StorageManager.isCeStorageUnlocked(user.id)
&& smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
@@ -410,7 +410,7 @@
// First look for stale data that doesn't belong, and check if things
// have changed since we did our last restorecon
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
- if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+ if (StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId)) {
throw new RuntimeException(
"Yikes, someone asked us to reconcile CE storage while " + userId
+ " was still locked; this would have caused massive data loss!");
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 30017be..3ed9f02 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,7 +24,6 @@
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1525,9 +1524,6 @@
ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
ai.volumeUuid = ps.getVolumeUuid();
ai.storageUuid = StorageManager.convert(ai.volumeUuid);
- if (ps.isDefaultToDeviceProtectedStorage()) {
- ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
- }
ai.setVersionCode(ps.getVersionCode());
ai.flags = ps.getFlags();
ai.privateFlags = ps.getPrivateFlags();
@@ -3560,7 +3556,7 @@
@Override
public int getPackageStartability(boolean safeMode, @NonNull String packageName, int callingUid,
@UserIdInt int userId) {
- final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId);
+ final boolean ceStorageUnlocked = StorageManager.isCeStorageUnlocked(userId);
final PackageStateInternal ps = getPackageStateInternal(packageName);
if (ps == null || shouldFilterApplication(ps, callingUid, userId)
|| !ps.getUserStateOrDefault(userId).isInstalled()) {
@@ -3575,7 +3571,7 @@
return PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
}
- if (!userKeyUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
+ if (!ceStorageUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
return PackageManagerService.PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED;
}
return PackageManagerService.PACKAGE_STARTABILITY_OK;
@@ -4596,6 +4592,7 @@
flags = updateFlagsForApplication(flags, userId);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
+ final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
enforceCrossUserPermission(
callingUid,
@@ -4607,7 +4604,7 @@
ArrayList<ApplicationInfo> list;
final ArrayMap<String, ? extends PackageStateInternal> packageStates =
getPackageStates();
- if (listUninstalled) {
+ if (listUninstalled || listArchivedOnly) {
list = new ArrayList<>(packageStates.size());
for (PackageStateInternal ps : packageStates.values()) {
ApplicationInfo ai;
@@ -4619,6 +4616,11 @@
if (!listApex && ps.getPkg().isApex()) {
continue;
}
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (listArchivedOnly && !userState.isInstalled()
+ && userState.getArchiveState() == null) {
+ continue;
+ }
if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 8bd2982..a10bae9 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 @@
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 2967818..2951ef6 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import static android.content.pm.Flags.preventSdkLibApp;
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -59,7 +59,6 @@
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.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.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 @@
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 @@
mContext = pm.mInjector.getContext();
mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
mPackageAbiHelper = pm.mInjector.getAbiHelper();
- mViewCompiler = pm.mInjector.getViewCompiler();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
}
@@ -996,7 +991,7 @@
}
final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
final boolean isSdkLibrary = packageToScan.isSdkLibrary();
- if (isApex || (isSdkLibrary && preventSdkLibApp())) {
+ if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
} else {
createdAppId.put(packageName, optimisticallyRegisterAppId(request));
@@ -1630,7 +1625,8 @@
synchronized (mPm.mLock) {
if (DEBUG_INSTALL) {
Slog.d(TAG,
- "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
+ "replacePackageLI: new=" + parsedPackage
+ + ", old=" + oldPackageState.getName());
}
ps = mPm.mSettings.getPackageLPr(pkgName11);
@@ -1789,7 +1785,7 @@
if (DEBUG_INSTALL) {
Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
- + ", old=" + oldPackage);
+ + ", old=" + oldPackageState.getName());
}
request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
request.setApexModuleName(oldPackageState.getApexModuleName());
@@ -1799,7 +1795,7 @@
if (DEBUG_INSTALL) {
Slog.d(TAG,
"replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
- + oldPackage);
+ + oldPackageState.getName());
}
}
} else { // new package install
@@ -2118,24 +2114,6 @@
// 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(
@@ -2396,12 +2374,6 @@
permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(),
permissionParamsBuilder.build(), userId);
- // Apply restricted settings on potentially dangerous packages.
- if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
- || installRequest.getPackageSource()
- == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
- enableRestrictedSettings(pkgName, pkg.getUid());
- }
}
installRequest.setName(pkgName);
installRequest.setAppId(pkg.getUid());
@@ -2416,16 +2388,13 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private void enableRestrictedSettings(String pkgName, int appId) {
+ private void enableRestrictedSettings(String pkgName, int appId, int userId) {
final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class);
- final int[] allUsersList = mPm.mUserManager.getUserIds();
- for (int userId : allUsersList) {
- final int uid = UserHandle.getUid(userId, appId);
- appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- uid,
- pkgName,
- AppOpsManager.MODE_ERRORED);
- }
+ final int uid = UserHandle.getUid(userId, appId);
+ appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid,
+ pkgName,
+ AppOpsManager.MODE_ERRORED);
}
/**
@@ -2538,13 +2507,6 @@
&& !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
@@ -2849,13 +2811,10 @@
mPm.mRequiredInstallerPackage,
/* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
-
// Work that needs to happen on first install within each user
- if (firstUserIds.length > 0) {
- for (int userId : firstUserIds) {
- mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
- userId);
- }
+ for (int userId : firstUserIds) {
+ mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
+ userId);
}
if (request.isAllNewUsers() && !update) {
@@ -2864,6 +2823,16 @@
mPm.notifyPackageChanged(packageName, request.getAppId());
}
+ for (int userId : firstUserIds) {
+ // Apply restricted settings on potentially dangerous packages. Needs to happen
+ // after appOpsManager is notified of the new package
+ if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ || request.getPackageSource()
+ == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+ enableRestrictedSettings(packageName, request.getAppId(), userId);
+ }
+ }
+
// Log current value of "unknown sources" setting
EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
getUnknownSourcesSettings());
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0ebd33b..4ed3163 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 @@
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 a161e8c..8b82a1c 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.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;
@@ -65,6 +66,7 @@
import android.content.pm.LauncherActivityInfoInternal;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -93,6 +95,7 @@
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;
@@ -111,6 +114,8 @@
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;
@@ -501,6 +506,9 @@
if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
return false;
}
+ if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+ return true;
+ }
if (mPackageManagerInternal.filterAppAccess(
packageName, Binder.getCallingUid(), userId)) {
return false;
@@ -512,15 +520,24 @@
@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) {
@@ -564,15 +581,16 @@
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);
}
@@ -584,6 +602,23 @@
}
}
+ 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;
@@ -649,23 +684,30 @@
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;
@@ -676,6 +718,26 @@
}
}
+ 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)
@@ -699,6 +761,100 @@
}
}
+ private boolean isPackageArchived(@NonNull String packageName, UserHandle user) {
+ return !getApplicationInfoForArchivedApp(packageName, user).isEmpty();
+ }
+
+ @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,
@@ -820,11 +976,17 @@
final int callingUid = injectBinderCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- final PackageInfo info = mPackageManagerInternal.getPackageInfo(packageName,
+ long callingFlag =
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- callingUid, user.getIdentifier());
- return info != null && info.applicationInfo.enabled;
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ if (Flags.archiving()) {
+ callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
+ }
+ final PackageInfo info =
+ mPackageManagerInternal.getPackageInfo(
+ packageName, callingFlag, callingUid, user.getIdentifier());
+ return info != null
+ && (info.applicationInfo.enabled || info.applicationInfo.isArchived);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1290,7 +1452,18 @@
if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
return false;
}
-
+ if (Flags.archiving() && component != null && component.getPackageName() != null) {
+ List<LauncherActivityInfoInternal> archiveActivities =
+ generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
+ if (!archiveActivities.isEmpty()) {
+ for (int i = 0; i < archiveActivities.size(); i++) {
+ if (archiveActivities.get(i).getComponentName().equals(component)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
final int callingUid = injectBinderCallingUid();
final int state = mPackageManagerInternal.getComponentEnabledSetting(component,
callingUid, user.getIdentifier());
@@ -1377,6 +1550,25 @@
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
+ // Only system launchers, which have access to recents should have access to this API.
+ // TODO(b/303803157): Add the new permission check if we decide to have one.
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
+ }
+ if (!canAccessProfile(user.getIdentifier(),
+ "Can't access LauncherUserInfo for another user")) {
+ return null;
+ }
+ long ident = injectClearCallingIdentity();
+ try {
+ return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier());
+ } finally {
+ injectRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 9ad8318..f5f5577 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -196,7 +196,8 @@
// If we're moving app data around, we need all the users unlocked
if (moveCompleteApp) {
for (int userId : installedUserIds) {
- if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+ if (StorageManager.isFileEncrypted()
+ && !StorageManager.isCeStorageUnlocked(userId)) {
freezer.close();
throw new PackageManagerException(MOVE_FAILED_LOCKED_USER,
"User " + userId + " must be unlocked");
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6a2ddc8..ea082cf 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -159,6 +159,9 @@
if (pkgSetting.getPkg().isCoreApp()) {
throw new IllegalStateException("Found a core app that's not important");
}
+ // Use REASON_FIRST_BOOT to query "pm.dexopt.first-boot" for the compiler filter, but
+ // the reason itself won't make it into the actual compiler reason because it will be
+ // overridden in otapreopt.cpp.
mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
PackageManagerService.REASON_FIRST_BOOT));
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 0fb1f7a..42a97f7 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.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;
@@ -38,16 +41,16 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+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;
import android.os.ParcelableException;
+import android.os.Process;
import android.os.SELinux;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -61,7 +64,6 @@
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;
@@ -162,15 +164,13 @@
});
}
- /**
- * Creates archived state for the package and user.
- */
- public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
+ /** Creates archived state for the package and user. */
+ private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
throws PackageManager.NameNotFoundException {
PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
Binder.getCallingUid(), userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
- verifyInstaller(responsibleInstallerPackage);
+ verifyInstaller(responsibleInstallerPackage, userId);
List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
userId);
@@ -215,10 +215,13 @@
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(),
@@ -248,7 +251,7 @@
@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.
@@ -256,7 +259,7 @@
}
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)) {
@@ -268,27 +271,34 @@
return iconFile.toPath();
}
- private void verifyInstaller(String installerPackage)
+ private void verifyInstaller(String installerPackage, int userId)
throws PackageManager.NameNotFoundException {
if (TextUtils.isEmpty(installerPackage)) {
throw new PackageManager.NameNotFoundException("No installer found");
}
- if (!verifySupportsUnarchival(installerPackage)) {
+ // Allow shell for easier development.
+ if ((Binder.getCallingUid() != Process.SHELL_UID)
+ && !verifySupportsUnarchival(installerPackage, userId)) {
throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
}
}
/**
- * @return true if installerPackage support unarchival:
- * - has an action Intent.ACTION_UNARCHIVE_PACKAGE,
- * - has permissions to install packages.
+ * Returns true if {@code installerPackage} supports unarchival being able to handle
+ * {@link Intent#ACTION_UNARCHIVE_PACKAGE}
*/
- public boolean verifySupportsUnarchival(String installerPackage) {
- // TODO(b/278553670) Check if installerPackage supports unarchival.
+ public boolean verifySupportsUnarchival(String installerPackage, int userId) {
if (TextUtils.isEmpty(installerPackage)) {
return false;
}
- return true;
+
+ Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage);
+
+ ParceledListSlice<ResolveInfo> intentReceivers =
+ Binder.withCleanCallingIdentity(
+ () -> mPm.queryIntentReceivers(mPm.snapshotComputer(),
+ intent, /* resolvedType= */ null, /* flags= */ 0, userId));
+ return intentReceivers != null && !intentReceivers.getList().isEmpty();
}
void requestUnarchive(
@@ -539,29 +549,6 @@
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;
@@ -571,18 +558,6 @@
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.
*/
@@ -619,8 +594,8 @@
/**
* 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");
}
@@ -634,9 +609,8 @@
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 1bb20b47..305e353 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,8 @@
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
@@ -621,6 +624,14 @@
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 +650,6 @@
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.
@@ -743,6 +746,22 @@
params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
}
+ if (Flags.rollbackLifetime()) {
+ if (params.rollbackLifetimeMillis > 0) {
+ if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackLifetimeMillis when rollback is not enabled");
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Setting rollback lifetime requires the MANAGE_ROLLBACKS permission");
+ }
+ } else if (params.rollbackLifetimeMillis < 0) {
+ throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+ }
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (isApex) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
@@ -1043,7 +1062,7 @@
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 +1542,61 @@
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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d0e5f96..1be28ca 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1282,6 +1282,7 @@
info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions;
info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
info.installFlags = params.installFlags;
+ info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
info.isMultiPackage = params.isMultiPackage;
info.isStaged = params.isStaged;
info.rollbackDataPolicy = params.rollbackDataPolicy;
@@ -3445,7 +3446,7 @@
}
if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
- getInstallSource().mInstallerPackageName)) {
+ getInstallSource().mInstallerPackageName, userId)) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
"Installer has to support unarchival in order to install archived "
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 61b6b24..c9303f2 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.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 @@
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 @@
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 @@
(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 @@
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 @@
mBackgroundDexOptService = injector.getBackgroundDexOptService();
mArtManagerService = injector.getArtManagerService();
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
- mViewCompiler = injector.getViewCompiler();
mSharedLibraries = mInjector.getSharedLibrariesImpl();
mBackgroundHandler = injector.getBackgroundHandler();
@@ -3360,7 +3357,7 @@
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
final int flags;
- if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
+ if (StorageManager.isCeStorageUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 5b770aab..ebf1c04 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.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 @@
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 @@
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 @@
mArtManagerServiceProducer = new Singleton<>(
artManagerServiceProducer);
mApexManagerProducer = new Singleton<>(apexManagerProducer);
- mViewCompilerProducer = new Singleton<>(viewCompilerProducer);
mIncrementalManagerProducer = new Singleton<>(
incrementalManagerProducer);
mDefaultAppProviderProducer = new Singleton<>(
@@ -339,10 +335,6 @@
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 ca57209..9428ef6 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.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 @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 9e7f043..a09e713 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 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 @@
}
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 42f4cfb..3cf5481 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -89,17 +90,14 @@
private static class Booleans {
@IntDef({
INSTALL_PERMISSION_FIXED,
- DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE
})
public @interface Flags {
}
private static final int INSTALL_PERMISSION_FIXED = 1;
- private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
- private static final int UPDATE_AVAILABLE = 1 << 2;
- private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
- private static final int PERSISTENT = 1 << 4;
+ private static final int UPDATE_AVAILABLE = 1 << 1;
+ private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
}
private int mBooleans;
@@ -123,10 +121,6 @@
@Nullable
private Map<String, Set<String>> mimeGroups;
- @Deprecated
- @Nullable
- private Set<String> mOldCodePaths;
-
@Nullable
private String[] usesSdkLibraries;
@@ -500,13 +494,6 @@
return this;
}
- public PackageSetting setDefaultToDeviceProtectedStorage(
- boolean defaultToDeviceProtectedStorage) {
- setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage);
- onChanged();
- return this;
- }
-
@Override
public boolean isExternalStorage() {
return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
@@ -524,12 +511,6 @@
return this;
}
- public PackageSetting setIsPersistent(boolean isPersistent) {
- setBoolean(Booleans.PERSISTENT, isPersistent);
- onChanged();
- return this;
- }
-
public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
mTargetSdkVersion = targetSdkVersion;
onChanged();
@@ -737,15 +718,6 @@
}
}
- if (mOldCodePaths != null) {
- if (other.mOldCodePaths != null) {
- mOldCodePaths.clear();
- mOldCodePaths.addAll(other.mOldCodePaths);
- } else {
- mOldCodePaths = null;
- }
- }
-
copyMimeGroups(other.mimeGroups);
pkgState.updateFrom(other.pkgState);
onChanged();
@@ -1443,12 +1415,6 @@
return this;
}
- public PackageSetting setOldCodePaths(Set<String> oldCodePaths) {
- mOldCodePaths = oldCodePaths;
- onChanged();
- return this;
- }
-
public PackageSetting setUsesSdkLibraries(String[] usesSdkLibraries) {
this.usesSdkLibraries = usesSdkLibraries;
onChanged();
@@ -1585,12 +1551,12 @@
*/
@Override
public boolean isDefaultToDeviceProtectedStorage() {
- return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+ return (getPrivateFlags() & PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
}
@Override
public boolean isPersistent() {
- return getBoolean(Booleans.PERSISTENT);
+ return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0;
}
@@ -1608,11 +1574,6 @@
//@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
@@ -1746,10 +1707,10 @@
}
@DataClass.Generated(
- time = 1696979728639L,
+ 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 com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ 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/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 8d8acfd4..7ea9e3f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,7 @@
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId, parsedPackage.isPersistent(),
+ newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
@@ -241,7 +241,7 @@
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getMimeGroups(), newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
@@ -464,8 +464,6 @@
+ " to " + volumeUuid);
pkgSetting.setVolumeUuid(volumeUuid);
}
- pkgSetting.setDefaultToDeviceProtectedStorage(
- parsedPackage.isDefaultToDeviceProtectedStorage());
SharedLibraryInfo sdkLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a39178e..440823c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -946,7 +946,6 @@
ret.setMimeGroups(p.getMimeGroups());
ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
ret.getPkgState().setUpdatedSystemApp(false);
- ret.setIsPersistent(p.isPersistent());
ret.setTargetSdkVersion(p.getTargetSdkVersion());
ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
}
@@ -1061,7 +1060,7 @@
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
@@ -1083,7 +1082,6 @@
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
@@ -1103,7 +1101,6 @@
.setSecondaryCpuAbi(secondaryCpuAbi)
.setLongVersionCode(versionCode)
.setMimeGroups(createMimeGroups(mimeGroupNames))
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash)
.setLastModifiedTime(codePath.lastModified());
@@ -1219,7 +1216,7 @@
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
@@ -1273,7 +1270,6 @@
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
@@ -3071,7 +3067,6 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
- serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
if (pkg.getRestrictUpdateHash() != null) {
serializer.attributeBytesBase64(null, "restrictUpdateHash",
@@ -3140,7 +3135,6 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
- serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
if (pkg.getRestrictUpdateHash() != null) {
serializer.attributeBytesBase64(null, "restrictUpdateHash",
@@ -3182,8 +3176,6 @@
if (pkg.getVolumeUuid() != null) {
serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
}
- serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage",
- pkg.isDefaultToDeviceProtectedStorage());
if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) {
serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride());
}
@@ -3878,7 +3870,6 @@
}
long versionCode = parser.getAttributeLong(null, "version", 0);
- boolean isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
null);
@@ -3901,7 +3892,6 @@
.setSecondaryCpuAbi(secondaryCpuAbiStr)
.setCpuAbiOverride(cpuAbiOverrideStr)
.setLongVersionCode(versionCode)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdateHash);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
@@ -3982,7 +3972,6 @@
String installInitiatingPackageName = null;
boolean installInitiatorUninstalled = false;
String volumeUuid = null;
- boolean defaultToDeviceProtectedStorage = false;
boolean updateAvailable = false;
int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
int pkgFlags = 0;
@@ -3997,7 +3986,6 @@
long loadingCompletedTime = 0;
UUID domainSetId;
String appMetadataFilePath = null;
- boolean isPersistent = false;
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
try {
@@ -4023,7 +4011,6 @@
}
versionCode = parser.getAttributeLong(null, "version", 0);
- isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
installerPackageName = parser.getAttributeValue(null, "installer");
@@ -4038,8 +4025,6 @@
installInitiatorUninstalled = parser.getAttributeBoolean(null,
"installInitiatorUninstalled", false);
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
- defaultToDeviceProtectedStorage = parser.getAttributeBoolean(
- null, "defaultToDeviceProtectedStorage", false);
categoryHint = parser.getAttributeInt(null, "categoryHint",
ApplicationInfo.CATEGORY_UNDEFINED);
appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
@@ -4179,7 +4164,6 @@
installInitiatorUninstalled);
packageSetting.setInstallSource(installSource)
.setVolumeUuid(volumeUuid)
- .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage)
.setCategoryOverride(categoryHint)
.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
.setPrimaryCpuAbi(primaryCpuAbiString)
@@ -4189,7 +4173,6 @@
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
.setAppMetadataFilePath(appMetadataFilePath)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdateHash);
// Handle legacy string here for single-user mode
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index c725cdc..70aa19a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -183,7 +183,7 @@
StorageManagerInternal.class);
for (UserInfo user : mPm.mUserManager.getUsers(false /* includeDying */)) {
final int flags;
- if (StorageManager.isUserKeyUnlocked(user.id)
+ if (StorageManager.isCeStorageUnlocked(user.id)
&& smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 29d99a73..e8cebef 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -120,8 +120,8 @@
return packageNames;
}
- final SuspendParams newSuspendParams =
- new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined);
+ final SuspendParams newSuspendParams = suspended
+ ? new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined) : null;
final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
@@ -156,8 +156,8 @@
final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
packageState.getUserStateOrDefault(userId).getSuspendParams();
- SuspendParams oldSuspendParams = suspendParamsMap == null
- ? null : suspendParamsMap.get(packageName);
+ final SuspendParams oldSuspendParams = suspendParamsMap == null
+ ? null : suspendParamsMap.get(callingPackage);
boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
if (suspended && !changed) {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 04cd183..0e7ce2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.graphics.Bitmap;
@@ -407,6 +408,11 @@
public abstract @NonNull UserInfo[] getUserInfos();
/**
+ * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found.
+ */
+ public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId);
+
+ /**
* Sets all default cross profile intent filters between {@code parentUserId} and
* {@code profileUserId}.
*/
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7331bc1..3430bb4d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -1426,7 +1427,7 @@
}
final boolean needToShowConfirmCredential = !dontAskCredential
&& mLockPatternUtils.isSecure(userId)
- && (!hasUnifiedChallenge || !StorageManager.isUserKeyUnlocked(userId));
+ && (!hasUnifiedChallenge || !StorageManager.isCeStorageUnlocked(userId));
if (needToShowConfirmCredential) {
if (onlyIfCredentialNotRequired) {
return false;
@@ -2137,6 +2138,19 @@
}
@Override
+ public boolean isForegroundUserAdmin() {
+ // No permission requirements for this API.
+ synchronized (mUsersLock) {
+ final int currentUserId = getCurrentUserId();
+ if (currentUserId != UserHandle.USER_NULL) {
+ final UserInfo userInfo = getUserInfoLU(currentUserId);
+ return userInfo != null && userInfo.isAdmin();
+ }
+ }
+ return false;
+ }
+
+ @Override
public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
if (!hasQueryOrCreateUsersPermission()
@@ -5094,9 +5108,9 @@
}
}
- 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
@@ -5898,17 +5912,18 @@
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
@@ -7153,14 +7168,32 @@
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) {
+ UserInfo userInfo;
+ synchronized (mUsersLock) {
+ userInfo = getUserInfoLU(userId);
+ }
+ if (userInfo != null) {
+ final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
+ final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
+ userDetails.getName(),
+ userInfo.serialNumber)
+ .build();
+ return uiInfo;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
int state;
synchronized (mUserStates) {
state = mUserStates.get(userId, -1);
}
- // Special case, in the stopping/shutdown state user key can still be unlocked
+ // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
- return StorageManager.isUserKeyUnlocked(userId);
+ return StorageManager.isCeStorageUnlocked(userId);
}
return (state == UserState.STATE_RUNNING_UNLOCKING)
|| (state == UserState.STATE_RUNNING_UNLOCKED);
@@ -7177,9 +7210,9 @@
synchronized (mUserStates) {
state = mUserStates.get(userId, -1);
}
- // Special case, in the stopping/shutdown state user key can still be unlocked
+ // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
- return StorageManager.isUserKeyUnlocked(userId);
+ return StorageManager.isCeStorageUnlocked(userId);
}
return state == UserState.STATE_RUNNING_UNLOCKED;
}
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 f4f03f4..ae47aa8 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 @@
}
/**
- * 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 00269224..0000000
--- 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/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 61e96ca..2ad8bcf 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -1016,8 +1016,8 @@
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1065,8 +1065,8 @@
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1113,9 +1113,9 @@
return Environment.getDataSystemDirectory();
}
- if (android.content.pm.Flags.nullableDataDir()
- && !ps.getUserStateOrDefault(userId).isInstalled()
- && !ps.getUserStateOrDefault(userId).dataExists()) {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()
+ && !ps.getUserStateOrDefault(userId).dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The app has been uninstalled for the user and the data dir has been deleted
return null;
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 46121dc..8240c47 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -18,7 +18,7 @@
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.content.pm.Flags.preventSdkLibApp;
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -404,7 +404,7 @@
try {
final File baseApk = new File(lite.getBaseApkPath());
- boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp();
+ boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
lite.getPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
@@ -458,7 +458,7 @@
final PackageLite lite = liteResult.getResult();
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
- boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp();
+ boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7c0fc99..077812b 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 @@
int mLidKeyboardAccessibility;
int mLidNavigationAccessibility;
int mShortPressOnPowerBehavior;
+ private boolean mShouldEarlyShortPressOnPower;
int mLongPressOnPowerBehavior;
long mLongPressOnPowerAssistantTimeoutMs;
int mVeryLongPressOnPowerBehavior;
@@ -2478,7 +2479,7 @@
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
initKeyCombinationRules();
- initSingleKeyGestureRules();
+ initSingleKeyGestureRules(injector.getLooper());
mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
}
@@ -2651,6 +2652,9 @@
@Override
void onPress(long downTime) {
+ if (mShouldEarlyShortPressOnPower) {
+ return;
+ }
powerPress(downTime, 1 /*count*/);
}
@@ -2684,6 +2688,13 @@
void onMultiPress(long downTime, int count) {
powerPress(downTime, count);
}
+
+ @Override
+ void onKeyUp(long eventTime, int count) {
+ if (mShouldEarlyShortPressOnPower && count == 1) {
+ powerPress(eventTime, 1 /*pressCount*/);
+ }
+ }
}
/**
@@ -2749,8 +2760,8 @@
}
}
- private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext);
+ private void initSingleKeyGestureRules(Looper looper) {
+ mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper);
mSingleKeyGestureDetector.addRule(new PowerKeyRule());
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule());
@@ -2913,6 +2924,9 @@
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/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 5fc0637..047555a 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -40,6 +40,7 @@
private static final int MSG_KEY_LONG_PRESS = 0;
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
+ private static final int MSG_KEY_UP = 3;
private int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -144,6 +145,13 @@
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
+ /**
+ * Callback executed upon each key up event that hasn't been processed by long press.
+ *
+ * @param eventTime the timestamp of this event.
+ * @param pressCount the number of presses detected leading up to this key up event.
+ */
+ void onKeyUp(long eventTime, int pressCount) {}
@Override
public String toString() {
@@ -171,8 +179,8 @@
}
}
- static SingleKeyGestureDetector get(Context context) {
- SingleKeyGestureDetector detector = new SingleKeyGestureDetector();
+ static SingleKeyGestureDetector get(Context context, Looper looper) {
+ SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper);
sDefaultLongPressTimeout = context.getResources().getInteger(
com.android.internal.R.integer.config_globalActionsKeyTimeout);
sDefaultVeryLongPressTimeout = context.getResources().getInteger(
@@ -180,8 +188,8 @@
return detector;
}
- private SingleKeyGestureDetector() {
- mHandler = new KeyHandler();
+ private SingleKeyGestureDetector(Looper looper) {
+ mHandler = new KeyHandler(looper);
}
void addRule(SingleKeyRule rule) {
@@ -330,6 +338,13 @@
}
if (event.getKeyCode() == mActiveRule.mKeyCode) {
+ // key-up action should always be triggered if not processed by long press.
+ Message msgKeyUp =
+ mHandler.obtainMessage(
+ MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule);
+ msgKeyUp.setAsynchronous(true);
+ mHandler.sendMessage(msgKeyUp);
+
// Directly trigger short press when max count is 1.
if (mActiveRule.getMaxMultiPressCount() == 1) {
if (DEBUG) {
@@ -402,8 +417,8 @@
}
private class KeyHandler extends Handler {
- KeyHandler() {
- super(Looper.myLooper());
+ KeyHandler(Looper looper) {
+ super(looper);
}
@Override
@@ -417,6 +432,12 @@
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
+ case MSG_KEY_UP:
+ if (DEBUG) {
+ Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode));
+ }
+ rule.onKeyUp(mLastDownTime, pressCount);
+ break;
case MSG_KEY_LONG_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
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 88c2e09..dd39fb0 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.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 @@
@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 @@
@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 @@
sessionSet.valueAt(j).close();
}
}
- mProcStatesCache.delete(uid);
}
});
}
@@ -292,15 +295,18 @@
@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 @@
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 @@
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 @@
}
@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 @@
@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 @@
@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 @@
/** 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 @@
@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 void setThreads(@NonNull int[] tids) {
- synchronized (mLock) {
+ synchronized (this) {
if (mHalSessionPtr == 0) {
return;
}
@@ -588,7 +596,7 @@
} 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 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 @@
}
}
- 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 @@
}
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 0000000..10c5362
--- /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/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index bc90f5c..aadd03b 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -19,7 +19,6 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
-import android.os.BatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
@@ -66,17 +65,7 @@
aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
for (int i = 0; i < configs.size(); i++) {
- mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i));
- }
- }
-
- private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats(
- AggregatedPowerStatsConfig.PowerComponent config) {
- switch (config.getPowerComponentId()) {
- case BatteryConsumer.POWER_COMPONENT_CPU:
- return new CpuAggregatedPowerStats(config);
- default:
- return new PowerComponentAggregatedPowerStats(config);
+ mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(configs.get(i));
}
}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 477c228..43fd15d 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -16,13 +16,16 @@
package com.android.server.power.stats;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.BatteryConsumer;
import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -73,6 +76,7 @@
private final int mPowerComponentId;
private @TrackedState int[] mTrackedDeviceStates;
private @TrackedState int[] mTrackedUidStates;
+ private AggregatedPowerStatsProcessor mProcessor = NO_OP_PROCESSOR;
PowerComponent(int powerComponentId) {
this.mPowerComponentId = powerComponentId;
@@ -94,6 +98,16 @@
return this;
}
+ /**
+ * Takes an object that should be invoked for every aggregated stats span
+ * before giving the aggregates stats to consumers. The processor can complete the
+ * aggregation process, for example by computing estimated power usage.
+ */
+ public PowerComponent setProcessor(@NonNull AggregatedPowerStatsProcessor processor) {
+ mProcessor = processor;
+ return this;
+ }
+
public int getPowerComponentId() {
return mPowerComponentId;
}
@@ -123,6 +137,11 @@
};
}
+ @NonNull
+ public AggregatedPowerStatsProcessor getProcessor() {
+ return mProcessor;
+ }
+
private boolean isTracked(int[] trackedStates, int state) {
if (trackedStates == null) {
return false;
@@ -153,4 +172,21 @@
public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
return mPowerComponents;
}
+
+ private static final AggregatedPowerStatsProcessor NO_OP_PROCESSOR =
+ new AggregatedPowerStatsProcessor() {
+ @Override
+ public void finish(PowerComponentAggregatedPowerStats stats) {
+ }
+
+ @Override
+ public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ return Arrays.toString(stats);
+ }
+
+ @Override
+ public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ return Arrays.toString(stats);
+ }
+ };
}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
new file mode 100644
index 0000000..5fd8ddf
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * The power estimation algorithm used by AggregatedPowerStatsProcessor can roughly be
+ * described like this:
+ *
+ * 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using
+ * a metric such as CPU time-in-state.
+ *
+ * 2. Combine estimates obtain in step 1, aggregating across states that are *not* tracked
+ * per UID.
+ *
+ * 2. For each UID, compute the proportion of the combined estimates in each state
+ * and attribute the corresponding portion of the total power estimate in that state to the UID.
+ */
+abstract class AggregatedPowerStatsProcessor {
+ private static final String TAG = "AggregatedPowerStatsProcessor";
+
+ private static final int INDEX_DOES_NOT_EXIST = -1;
+ private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
+
+ abstract void finish(PowerComponentAggregatedPowerStats stats);
+
+ abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
+
+ abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
+
+ protected static class PowerEstimationPlan {
+ private final AggregatedPowerStatsConfig.PowerComponent mConfig;
+ public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
+ public List<CombinedDeviceStateEstimate> combinedDeviceStateEstimations = new ArrayList<>();
+ public List<UidStateEstimate> uidStateEstimates = new ArrayList<>();
+
+ public PowerEstimationPlan(AggregatedPowerStatsConfig.PowerComponent config) {
+ mConfig = config;
+ addDeviceStateEstimations();
+ combineDeviceStateEstimations();
+ addUidStateEstimations();
+ }
+
+ private void addDeviceStateEstimations() {
+ MultiStateStats.States[] config = mConfig.getDeviceStateConfig();
+ int[][] deviceStateCombinations = getAllTrackedStateCombinations(config);
+ for (int[] deviceStateCombination : deviceStateCombinations) {
+ deviceStateEstimations.add(
+ new DeviceStateEstimation(config, deviceStateCombination));
+ }
+ }
+
+ private void combineDeviceStateEstimations() {
+ MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig();
+ MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig();
+ MultiStateStats.States[] deviceStatesTrackedPerUid =
+ new MultiStateStats.States[deviceStateConfig.length];
+
+ for (int i = 0; i < deviceStateConfig.length; i++) {
+ if (!deviceStateConfig[i].isTracked()) {
+ continue;
+ }
+
+ int index = findTrackedStateByName(uidStateConfig, deviceStateConfig[i].getName());
+ if (index != INDEX_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) {
+ deviceStatesTrackedPerUid[i] = deviceStateConfig[i];
+ }
+ }
+
+ combineDeviceStateEstimationsRecursively(deviceStateConfig, deviceStatesTrackedPerUid,
+ new int[deviceStateConfig.length], 0);
+ }
+
+ private void combineDeviceStateEstimationsRecursively(
+ MultiStateStats.States[] deviceStateConfig,
+ MultiStateStats.States[] deviceStatesTrackedPerUid, int[] stateValues, int state) {
+ if (state >= deviceStateConfig.length) {
+ DeviceStateEstimation dse = getDeviceStateEstimate(stateValues);
+ CombinedDeviceStateEstimate cdse = getCombinedDeviceStateEstimate(
+ deviceStatesTrackedPerUid, stateValues);
+ if (cdse == null) {
+ cdse = new CombinedDeviceStateEstimate(deviceStatesTrackedPerUid, stateValues);
+ combinedDeviceStateEstimations.add(cdse);
+ }
+ cdse.deviceStateEstimations.add(dse);
+ return;
+ }
+
+ if (deviceStateConfig[state].isTracked()) {
+ for (int stateValue = 0;
+ stateValue < deviceStateConfig[state].getLabels().length;
+ stateValue++) {
+ stateValues[state] = stateValue;
+ combineDeviceStateEstimationsRecursively(deviceStateConfig,
+ deviceStatesTrackedPerUid, stateValues, state + 1);
+ }
+ } else {
+ combineDeviceStateEstimationsRecursively(deviceStateConfig,
+ deviceStatesTrackedPerUid, stateValues, state + 1);
+ }
+ }
+
+ private void addUidStateEstimations() {
+ MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig();
+ MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig();
+ MultiStateStats.States[] uidStatesTrackedForDevice =
+ new MultiStateStats.States[uidStateConfig.length];
+ MultiStateStats.States[] uidStatesNotTrackedForDevice =
+ new MultiStateStats.States[uidStateConfig.length];
+
+ for (int i = 0; i < uidStateConfig.length; i++) {
+ if (!uidStateConfig[i].isTracked()) {
+ continue;
+ }
+
+ int index = findTrackedStateByName(deviceStateConfig, uidStateConfig[i].getName());
+ if (index != INDEX_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) {
+ uidStatesTrackedForDevice[i] = uidStateConfig[i];
+ } else {
+ uidStatesNotTrackedForDevice[i] = uidStateConfig[i];
+ }
+ }
+
+ @AggregatedPowerStatsConfig.TrackedState
+ int[][] uidStateCombinations = getAllTrackedStateCombinations(uidStateConfig);
+ for (int[] stateValues : uidStateCombinations) {
+ CombinedDeviceStateEstimate combined =
+ getCombinedDeviceStateEstimate(uidStatesTrackedForDevice, stateValues);
+ if (combined == null) {
+ // This is not supposed to be possible
+ Log.wtf(TAG, "Mismatch in UID and combined device states: "
+ + concatLabels(uidStatesTrackedForDevice, stateValues));
+ continue;
+ }
+ UidStateEstimate uidStateEstimate = getUidStateEstimate(combined);
+ if (uidStateEstimate == null) {
+ uidStateEstimate = new UidStateEstimate(combined, uidStatesNotTrackedForDevice);
+ uidStateEstimates.add(uidStateEstimate);
+ }
+ uidStateEstimate.proportionalEstimates.add(
+ new UidStateProportionalEstimate(stateValues));
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Step 1. Compute device-wide power estimates for state combinations:\n");
+ for (DeviceStateEstimation deviceStateEstimation : deviceStateEstimations) {
+ sb.append(" ").append(deviceStateEstimation.id).append("\n");
+ }
+ sb.append("Step 2. Combine device-wide estimates that are untracked per UID:\n");
+ boolean any = false;
+ for (CombinedDeviceStateEstimate cdse : combinedDeviceStateEstimations) {
+ if (cdse.deviceStateEstimations.size() <= 1) {
+ continue;
+ }
+ any = true;
+ sb.append(" ").append(cdse.id).append(": ");
+ for (int i = 0; i < cdse.deviceStateEstimations.size(); i++) {
+ if (i != 0) {
+ sb.append(" + ");
+ }
+ sb.append(cdse.deviceStateEstimations.get(i).id);
+ }
+ sb.append("\n");
+ }
+ if (!any) {
+ sb.append(" N/A\n");
+ }
+ sb.append("Step 3. Proportionally distribute power estimates to UIDs:\n");
+ for (UidStateEstimate uidStateEstimate : uidStateEstimates) {
+ sb.append(" ").append(uidStateEstimate.combinedDeviceStateEstimate.id)
+ .append("\n among: ");
+ for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) {
+ UidStateProportionalEstimate uspe =
+ uidStateEstimate.proportionalEstimates.get(i);
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(concatLabels(uidStateEstimate.states, uspe.stateValues));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ @Nullable
+ public DeviceStateEstimation getDeviceStateEstimate(
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues);
+ for (int i = 0; i < deviceStateEstimations.size(); i++) {
+ DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i);
+ if (deviceStateEstimation.id.equals(label)) {
+ return deviceStateEstimation;
+ }
+ }
+ return null;
+ }
+
+ public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate(
+ MultiStateStats.States[] deviceStates,
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ String label = concatLabels(deviceStates, stateValues);
+ for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) {
+ CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i);
+ if (cdse.id.equals(label)) {
+ return cdse;
+ }
+ }
+ return null;
+ }
+
+ public UidStateEstimate getUidStateEstimate(CombinedDeviceStateEstimate combined) {
+ for (int i = 0; i < uidStateEstimates.size(); i++) {
+ UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
+ if (uidStateEstimate.combinedDeviceStateEstimate == combined) {
+ return uidStateEstimate;
+ }
+ }
+ return null;
+ }
+
+ public void resetIntermediates() {
+ for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+ deviceStateEstimations.get(i).intermediates = null;
+ }
+ for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+ deviceStateEstimations.get(i).intermediates = null;
+ }
+ for (int i = uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
+ List<UidStateProportionalEstimate> proportionalEstimates =
+ uidStateEstimate.proportionalEstimates;
+ for (int j = proportionalEstimates.size() - 1; j >= 0; j--) {
+ proportionalEstimates.get(j).intermediates = null;
+ }
+ }
+ }
+ }
+
+ protected static class DeviceStateEstimation {
+ public final String id;
+ @AggregatedPowerStatsConfig.TrackedState
+ public final int[] stateValues;
+ public Object intermediates;
+
+ public DeviceStateEstimation(MultiStateStats.States[] config,
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ id = concatLabels(config, stateValues);
+ this.stateValues = stateValues;
+ }
+ }
+
+ protected static class CombinedDeviceStateEstimate {
+ public final String id;
+ public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
+ public Object intermediates;
+
+ public CombinedDeviceStateEstimate(MultiStateStats.States[] config,
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ id = concatLabels(config, stateValues);
+ }
+ }
+
+ protected static class UidStateEstimate {
+ public final MultiStateStats.States[] states;
+ public CombinedDeviceStateEstimate combinedDeviceStateEstimate;
+ public List<UidStateProportionalEstimate> proportionalEstimates = new ArrayList<>();
+
+ public UidStateEstimate(CombinedDeviceStateEstimate combined,
+ MultiStateStats.States[] states) {
+ combinedDeviceStateEstimate = combined;
+ this.states = states;
+ }
+ }
+
+ protected static class UidStateProportionalEstimate {
+ @AggregatedPowerStatsConfig.TrackedState
+ public final int[] stateValues;
+ public Object intermediates;
+
+ protected UidStateProportionalEstimate(
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ this.stateValues = stateValues;
+ }
+ }
+
+ private static int findTrackedStateByName(MultiStateStats.States[] states, String name) {
+ for (int i = 0; i < states.length; i++) {
+ if (states[i].getName().equals(name)) {
+ return i;
+ }
+ }
+ return INDEX_DOES_NOT_EXIST;
+ }
+
+ @NonNull
+ private static String concatLabels(MultiStateStats.States[] config,
+ @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ List<String> labels = new ArrayList<>();
+ for (int state = 0; state < config.length; state++) {
+ if (config[state] != null && config[state].isTracked()) {
+ labels.add(config[state].getName()
+ + "=" + config[state].getLabels()[stateValues[state]]);
+ }
+ }
+ Collections.sort(labels);
+ return labels.toString();
+ }
+
+ @AggregatedPowerStatsConfig.TrackedState
+ private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) {
+ List<int[]> combinations = new ArrayList<>();
+ MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> {
+ combinations.add(Arrays.copyOf(stateValues, stateValues.length));
+ });
+ return combinations.toArray(new int[combinations.size()][0]);
+ }
+
+ public static double uCtoMah(long chargeUC) {
+ return chargeUC * MILLIAMPHOUR_PER_MICROCOULOMB;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index a9c2bc2..a6558e0 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -10937,7 +10937,8 @@
}
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
- mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
+ () -> mBatteryVoltageMv, mHandler,
+ mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
mStartCount++;
@@ -14437,6 +14438,7 @@
final int level, /* not final */ int temp, final int voltageMv, final int chargeUah,
final int chargeFullUah, final long chargeTimeToFullSeconds,
final long elapsedRealtimeMs, final long uptimeMs, final long currentTimeMs) {
+
// Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
temp = Math.max(0, temp);
@@ -15621,18 +15623,6 @@
}
}
- @GuardedBy("this")
- private void dumpCpuPowerBracketsLocked(PrintWriter pw) {
- pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):");
- final int bracketCount = mPowerProfile.getCpuPowerBracketCount();
- for (int bracket = 0; bracket < bracketCount; bracket++) {
- pw.print(" ");
- pw.print(bracket);
- pw.print(": ");
- pw.println(mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, bracket));
- }
- }
-
/**
* Dump EnergyConsumer stats
*/
@@ -16989,8 +16979,10 @@
pw.println();
dumpConstantsLocked(pw);
- pw.println();
- dumpCpuPowerBracketsLocked(pw);
+ if (mCpuPowerStatsCollector != null) {
+ pw.println();
+ mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw);
+ }
pw.println();
dumpEnergyConsumerStatsLocked(pw);
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
deleted file mode 100644
index fbf6928..0000000
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
- CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
- super(config);
- }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
new file mode 100644
index 0000000..f40eef2
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryStats;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProcessor {
+ private static final String TAG = "CpuAggregatedPowerStatsProcessor";
+
+ private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+ private static final int UNKNOWN = -1;
+
+ private final CpuScalingPolicies mCpuScalingPolicies;
+ // Number of CPU core clusters
+ private final int mCpuClusterCount;
+ // Total number of CPU scaling steps across all clusters
+ private final int mCpuScalingStepCount;
+ // Map of scaling step to the corresponding core cluster mScalingStepToCluster[step]->cluster
+ private final int[] mScalingStepToCluster;
+ // Average power consumed by the CPU when it is powered up (per power_profile.xml)
+ private final double mPowerMultiplierForCpuActive;
+ // Average power consumed by each cluster when it is powered up (per power_profile.xml)
+ private final double[] mPowerMultipliersByCluster;
+ // Average power consumed by each scaling step when running code (per power_profile.xml)
+ private final double[] mPowerMultipliersByScalingStep;
+ // A map used to combine energy consumers into a smaller set, in case power brackets
+ // are defined in a way that does not allow an unambiguous mapping of energy consumers to
+ // brackets
+ private int[] mEnergyConsumerToCombinedEnergyConsumerMap;
+ // A map of combined energy consumers to the corresponding collections of power brackets.
+ // For example, if there are two CPU_CLUSTER rails and each maps to three brackets,
+ // this map will look like this:
+ // 0 : [0, 1, 2]
+ // 1 : [3, 4, 5]
+ private int[][] mCombinedEnergyConsumerToPowerBracketMap;
+
+ // Cached reference to a PowerStats descriptor. Almost never changes in practice,
+ // helping to avoid reparsing the descriptor for every PowerStats span.
+ private PowerStats.Descriptor mLastUsedDescriptor;
+ // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when
+ // mLastUsedDescriptor changes
+ private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+ // Sequence of steps for power estimation and intermediate results.
+ private PowerEstimationPlan mPlan;
+
+ // Temp array for retrieval of device power stats, to avoid repeated allocations
+ private long[] mTmpDeviceStatsArray;
+ // Temp array for retrieval of UID power stats, to avoid repeated allocations
+ private long[] mTmpUidStatsArray;
+
+ public CpuAggregatedPowerStatsProcessor(PowerProfile powerProfile,
+ CpuScalingPolicies scalingPolicies) {
+ mCpuScalingPolicies = scalingPolicies;
+ mCpuScalingStepCount = scalingPolicies.getScalingStepCount();
+ mScalingStepToCluster = new int[mCpuScalingStepCount];
+ mPowerMultipliersByScalingStep = new double[mCpuScalingStepCount];
+
+ int step = 0;
+ int[] policies = scalingPolicies.getPolicies();
+ mCpuClusterCount = policies.length;
+ mPowerMultipliersByCluster = new double[mCpuClusterCount];
+ for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+ int policy = policies[cluster];
+ mPowerMultipliersByCluster[cluster] =
+ powerProfile.getAveragePowerForCpuScalingPolicy(policy) / HOUR_IN_MILLIS;
+ int[] frequencies = scalingPolicies.getFrequencies(policy);
+ for (int i = 0; i < frequencies.length; i++) {
+ mScalingStepToCluster[step] = cluster;
+ mPowerMultipliersByScalingStep[step] =
+ powerProfile.getAveragePowerForCpuScalingStep(policy, i) / HOUR_IN_MILLIS;
+ step++;
+ }
+ }
+ mPowerMultiplierForCpuActive =
+ powerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE) / HOUR_IN_MILLIS;
+ }
+
+ private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+ if (descriptor.equals(mLastUsedDescriptor)) {
+ return;
+ }
+
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+ mStatsLayout.fromExtras(descriptor.extras);
+
+ mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+ mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+ }
+
+ /**
+ * Temporary struct to capture intermediate results of power estimation.
+ */
+ private static final class Intermediates {
+ public long uptime;
+ // Sum of all time-in-step values, combining all time-in-step durations across all cores.
+ public long cumulativeTime;
+ // CPU activity durations per cluster
+ public long[] timeByCluster;
+ // Sums of time-in-step values, aggregated by cluster, combining all cores in the cluster.
+ public long[] cumulativeTimeByCluster;
+ public long[] timeByScalingStep;
+ public double[] powerByCluster;
+ public double[] powerByScalingStep;
+ public long[] powerByEnergyConsumer;
+ }
+
+ /**
+ * Temporary struct to capture intermediate results of power estimation.
+ */
+ private static class DeviceStatsIntermediates {
+ public double power;
+ public long[] timeByBracket;
+ public double[] powerByBracket;
+ }
+
+ @Override
+ public void finish(PowerComponentAggregatedPowerStats stats) {
+ if (stats.getPowerStatsDescriptor() == null) {
+ return;
+ }
+
+ unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor());
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) {
+ initEnergyConsumerToPowerBracketMaps();
+ }
+ }
+
+ Intermediates intermediates = new Intermediates();
+
+ int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+ if (cpuScalingStepCount != mCpuScalingStepCount) {
+ Log.e(TAG, "Mismatched CPU scaling step count in PowerStats: " + cpuScalingStepCount
+ + ", expected: " + mCpuScalingStepCount);
+ return;
+ }
+
+ int clusterCount = mStatsLayout.getCpuClusterCount();
+ if (clusterCount != mCpuClusterCount) {
+ Log.e(TAG, "Mismatched CPU cluster count in PowerStats: " + clusterCount
+ + ", expected: " + mCpuClusterCount);
+ return;
+ }
+
+ computeTotals(stats, intermediates);
+ if (intermediates.cumulativeTime == 0) {
+ return;
+ }
+
+ estimatePowerByScalingStep(intermediates);
+ estimatePowerByDeviceState(stats, intermediates);
+ combineDeviceStateEstimates();
+
+ ArrayList<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+ if (!uids.isEmpty()) {
+ for (int uid : uids) {
+ for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+ estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i));
+ }
+ }
+ }
+ mPlan.resetIntermediates();
+ }
+
+ /*
+ * Populate data structures (two maps) needed to use power rail data, aka energy consumers,
+ * to attribute power usage to apps.
+ *
+ * At this point, the algorithm covers only the most basic cases:
+ * - Each cluster is mapped to unique power brackets (possibly multiple for each cluster):
+ * CL_0: [bracket0, bracket1]
+ * CL_1: [bracket3]
+ * In this case, the consumed energy is distributed to the corresponding brackets
+ * proportionally.
+ * - Brackets span multiple clusters:
+ * CL_0: [bracket0, bracket1]
+ * CL_1: [bracket1, bracket2]
+ * CL_2: [bracket3, bracket4]
+ * In this case, we combine energy consumers into groups unambiguously mapped to
+ * brackets. In the above example, consumed energy for CL_0 and CL_1 will be combined
+ * because they both map to the same power bracket (bracket1):
+ * (CL_0+CL_1): [bracket0, bracket1, bracket2]
+ * CL_2: [bracket3, bracket4]
+ */
+ private void initEnergyConsumerToPowerBracketMaps() {
+ int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+ int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+
+ mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
+ mCombinedEnergyConsumerToPowerBracketMap = new int[energyConsumerCount][];
+
+ int[] policies = mCpuScalingPolicies.getPolicies();
+ if (energyConsumerCount == policies.length) {
+ int[] scalingStepToPowerBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
+ ArraySet<Integer>[] clusterToBrackets = new ArraySet[policies.length];
+ int step = 0;
+ for (int cluster = 0; cluster < policies.length; cluster++) {
+ int[] freqs = mCpuScalingPolicies.getFrequencies(policies[cluster]);
+ clusterToBrackets[cluster] = new ArraySet<>(freqs.length);
+ for (int j = 0; j < freqs.length; j++) {
+ clusterToBrackets[cluster].add(scalingStepToPowerBracketMap[step++]);
+ }
+ }
+
+ ArraySet<Integer>[] combinedEnergyConsumers = new ArraySet[policies.length];
+ int combinedEnergyConsumersCount = 0;
+
+ for (int cluster = 0; cluster < clusterToBrackets.length; cluster++) {
+ int combineWith = UNKNOWN;
+ for (int i = 0; i < combinedEnergyConsumersCount; i++) {
+ if (containsAny(combinedEnergyConsumers[i], clusterToBrackets[cluster])) {
+ combineWith = i;
+ break;
+ }
+ }
+ if (combineWith != UNKNOWN) {
+ mEnergyConsumerToCombinedEnergyConsumerMap[cluster] = combineWith;
+ combinedEnergyConsumers[combineWith].addAll(clusterToBrackets[cluster]);
+ } else {
+ mEnergyConsumerToCombinedEnergyConsumerMap[cluster] =
+ combinedEnergyConsumersCount;
+ combinedEnergyConsumers[combinedEnergyConsumersCount++] =
+ clusterToBrackets[cluster];
+ }
+ }
+
+ for (int i = combinedEnergyConsumers.length - 1; i >= 0; i--) {
+ mCombinedEnergyConsumerToPowerBracketMap[i] =
+ new int[combinedEnergyConsumers[i].size()];
+ for (int j = combinedEnergyConsumers[i].size() - 1; j >= 0; j--) {
+ mCombinedEnergyConsumerToPowerBracketMap[i][j] =
+ combinedEnergyConsumers[i].valueAt(j);
+ }
+ }
+ } else {
+ // All CPU cluster energy consumers are combined into one, which is
+ // distributed proportionally to all power brackets.
+ int[] map = new int[powerBracketCount];
+ for (int i = 0; i < map.length; i++) {
+ map[i] = i;
+ }
+ mCombinedEnergyConsumerToPowerBracketMap[0] = map;
+ }
+ }
+
+ private static boolean containsAny(ArraySet<Integer> set1, ArraySet<Integer> set2) {
+ for (int i = 0; i < set2.size(); i++) {
+ if (set1.contains(set2.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void computeTotals(PowerComponentAggregatedPowerStats stats,
+ Intermediates intermediates) {
+ intermediates.timeByScalingStep = new long[mCpuScalingStepCount];
+ intermediates.timeByCluster = new long[mCpuClusterCount];
+ intermediates.cumulativeTimeByCluster = new long[mCpuClusterCount];
+
+ List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations;
+ for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(i);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues)) {
+ continue;
+ }
+
+ intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray);
+
+ for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+ intermediates.timeByCluster[cluster] +=
+ mStatsLayout.getTimeByCluster(mTmpDeviceStatsArray, cluster);
+ }
+
+ for (int step = 0; step < mCpuScalingStepCount; step++) {
+ long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step);
+ intermediates.cumulativeTime += timeInStep;
+ intermediates.timeByScalingStep[step] += timeInStep;
+ intermediates.cumulativeTimeByCluster[mScalingStepToCluster[step]] += timeInStep;
+ }
+ }
+ }
+
+ private void estimatePowerByScalingStep(Intermediates intermediates) {
+ // CPU consumes some power when it's on - no matter which cores are running.
+ double cpuActivePower = mPowerMultiplierForCpuActive * intermediates.uptime;
+
+ // Additionally, every cluster consumes some power when any of its cores are running
+ intermediates.powerByCluster = new double[mCpuClusterCount];
+ for (int cluster = 0; cluster < mCpuClusterCount; cluster++) {
+ intermediates.powerByCluster[cluster] =
+ mPowerMultipliersByCluster[cluster] * intermediates.timeByCluster[cluster];
+ }
+
+ // Finally, additional power is consumed depending on the frequency scaling
+ intermediates.powerByScalingStep = new double[mCpuScalingStepCount];
+ for (int step = 0; step < mCpuScalingStepCount; step++) {
+ int cluster = mScalingStepToCluster[step];
+
+ double power;
+
+ // Distribute base power proportionally
+ power = cpuActivePower * intermediates.timeByScalingStep[step]
+ / intermediates.cumulativeTime;
+
+ // Distribute per-cluster power proportionally
+ long cumulativeTimeInCluster = intermediates.cumulativeTimeByCluster[cluster];
+ if (cumulativeTimeInCluster != 0) {
+ power += intermediates.powerByCluster[cluster]
+ * intermediates.timeByScalingStep[step]
+ / cumulativeTimeInCluster;
+ }
+
+ power += mPowerMultipliersByScalingStep[step] * intermediates.timeByScalingStep[step];
+
+ intermediates.powerByScalingStep[step] = power;
+ }
+ }
+
+ private void estimatePowerByDeviceState(PowerComponentAggregatedPowerStats stats,
+ Intermediates intermediates) {
+ int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+ int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+ int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap();
+ int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+ List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations;
+ for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) {
+ DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse);
+ deviceStateEstimation.intermediates = new DeviceStatsIntermediates();
+ DeviceStatsIntermediates deviceStatsIntermediates =
+ (DeviceStatsIntermediates) deviceStateEstimation.intermediates;
+ deviceStatsIntermediates.timeByBracket = new long[powerBracketCount];
+ deviceStatsIntermediates.powerByBracket = new double[powerBracketCount];
+
+ stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues);
+ for (int step = 0; step < cpuScalingStepCount; step++) {
+ if (intermediates.timeByScalingStep[step] == 0) {
+ continue;
+ }
+
+ long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step);
+ double stepPower = intermediates.powerByScalingStep[step] * timeInStep
+ / intermediates.timeByScalingStep[step];
+
+ int bracket = scalingStepToBracketMap[step];
+ deviceStatsIntermediates.timeByBracket[bracket] += timeInStep;
+ deviceStatsIntermediates.powerByBracket[bracket] += stepPower;
+ }
+
+ if (energyConsumerCount != 0) {
+ adjustEstimatesUsingEnergyConsumers(intermediates, deviceStatsIntermediates);
+ }
+
+ double power = 0;
+ for (int i = deviceStatsIntermediates.powerByBracket.length - 1; i >= 0; i--) {
+ power += deviceStatsIntermediates.powerByBracket[i];
+ }
+ deviceStatsIntermediates.power = power;
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+ stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+
+ private void adjustEstimatesUsingEnergyConsumers(
+ Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) {
+ int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+ if (energyConsumerCount == 0) {
+ return;
+ }
+
+ if (intermediates.powerByEnergyConsumer == null) {
+ intermediates.powerByEnergyConsumer = new long[energyConsumerCount];
+ } else {
+ Arrays.fill(intermediates.powerByEnergyConsumer, 0);
+ }
+ for (int i = 0; i < energyConsumerCount; i++) {
+ intermediates.powerByEnergyConsumer[mEnergyConsumerToCombinedEnergyConsumerMap[i]] +=
+ mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i);
+ }
+
+ for (int combinedConsumer = mCombinedEnergyConsumerToPowerBracketMap.length - 1;
+ combinedConsumer >= 0; combinedConsumer--) {
+ int[] combinedEnergyConsumerToPowerBracketMap =
+ mCombinedEnergyConsumerToPowerBracketMap[combinedConsumer];
+ if (combinedEnergyConsumerToPowerBracketMap == null) {
+ continue;
+ }
+
+ double consumedEnergy = uCtoMah(intermediates.powerByEnergyConsumer[combinedConsumer]);
+
+ double totalModeledPower = 0;
+ for (int bracket : combinedEnergyConsumerToPowerBracketMap) {
+ totalModeledPower += deviceStatsIntermediates.powerByBracket[bracket];
+ }
+ if (totalModeledPower == 0) {
+ continue;
+ }
+
+ for (int bracket : combinedEnergyConsumerToPowerBracketMap) {
+ deviceStatsIntermediates.powerByBracket[bracket] =
+ consumedEnergy * deviceStatsIntermediates.powerByBracket[bracket]
+ / totalModeledPower;
+ }
+ }
+ }
+
+ private void combineDeviceStateEstimates() {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+ DeviceStatsIntermediates cdseIntermediates = new DeviceStatsIntermediates();
+ cdse.intermediates = cdseIntermediates;
+ int bracketCount = mStatsLayout.getCpuPowerBracketCount();
+ cdseIntermediates.timeByBracket = new long[bracketCount];
+ cdseIntermediates.powerByBracket = new double[bracketCount];
+ List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+ for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation dse = deviceStateEstimations.get(j);
+ DeviceStatsIntermediates intermediates =
+ (DeviceStatsIntermediates) dse.intermediates;
+ cdseIntermediates.power += intermediates.power;
+ for (int k = 0; k < bracketCount; k++) {
+ cdseIntermediates.timeByBracket[k] += intermediates.timeByBracket[k];
+ cdseIntermediates.powerByBracket[k] += intermediates.powerByBracket[k];
+ }
+ }
+ }
+ }
+
+ private void estimateUidPowerConsumption(PowerComponentAggregatedPowerStats stats, int uid,
+ UidStateEstimate uidStateEstimate) {
+ CombinedDeviceStateEstimate combinedDeviceStateEstimate =
+ uidStateEstimate.combinedDeviceStateEstimate;
+ DeviceStatsIntermediates cdsIntermediates =
+ (DeviceStatsIntermediates) combinedDeviceStateEstimate.intermediates;
+ for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) {
+ UidStateProportionalEstimate proportionalEstimate =
+ uidStateEstimate.proportionalEstimates.get(i);
+ if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+ continue;
+ }
+
+ double power = 0;
+ for (int bracket = 0; bracket < mStatsLayout.getCpuPowerBracketCount(); bracket++) {
+ if (cdsIntermediates.timeByBracket[bracket] == 0) {
+ continue;
+ }
+
+ long timeInBracket = mStatsLayout.getUidTimeByPowerBracket(mTmpUidStatsArray,
+ bracket);
+ if (timeInBracket == 0) {
+ continue;
+ }
+
+ power += cdsIntermediates.powerByBracket[bracket] * timeInBracket
+ / cdsIntermediates.timeByBracket[bracket];
+ }
+
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ }
+ }
+
+ @Override
+ public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ StringBuilder sb = new StringBuilder();
+ int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
+ sb.append("steps: [");
+ for (int step = 0; step < cpuScalingStepCount; step++) {
+ if (step != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
+ }
+ int clusterCount = mStatsLayout.getCpuClusterCount();
+ sb.append("] clusters: [");
+ for (int cluster = 0; cluster < clusterCount; cluster++) {
+ if (cluster != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
+ }
+ sb.append("] uptime: ").append(mStatsLayout.getUptime(stats));
+ int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount();
+ if (energyConsumerCount > 0) {
+ sb.append(" energy: [");
+ for (int i = 0; i < energyConsumerCount; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStatsLayout.getConsumedEnergy(stats, i));
+ }
+ sb.append("]");
+ }
+ sb.append(" power: ").append(
+ BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
+ return sb.toString();
+ }
+
+ @Override
+ public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
+ for (int bracket = 0; bracket < powerBracketCount; bracket++) {
+ if (bracket != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
+ }
+ sb.append("] power: ").append(
+ BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 376ca89..a388932 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -16,19 +16,39 @@
package com.android.server.power.stats;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
import android.os.BatteryConsumer;
import android.os.Handler;
import android.os.PersistableBundle;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.Keep;
import com.android.internal.annotations.VisibleForNative;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
+import com.android.server.LocalServices;
import com.android.server.power.optimization.Flags;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
/**
* Collects snapshots of power-related system statistics.
* <p>
@@ -36,45 +56,350 @@
* constructor. Thus the object is not thread-safe except where noted.
*/
public class CpuPowerStatsCollector extends PowerStatsCollector {
+ private static final String TAG = "CpuPowerStatsCollector";
private static final long NANOS_PER_MILLIS = 1000000;
+ private static final long ENERGY_UNSPECIFIED = -1;
+ private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
+ private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
+ private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
+ private boolean mIsInitialized;
+ private final CpuScalingPolicies mCpuScalingPolicies;
+ private final PowerProfile mPowerProfile;
private final KernelCpuStatsReader mKernelCpuStatsReader;
- private final int[] mScalingStepToPowerBracketMap;
- private final long[] mTempUidStats;
+ private final Supplier<PowerStatsInternal> mPowerStatsSupplier;
+ private final IntSupplier mVoltageSupplier;
+ private final int mDefaultCpuPowerBrackets;
+ private final int mDefaultCpuPowerBracketsPerEnergyConsumer;
+ private long[] mCpuTimeByScalingStep;
+ private long[] mTempCpuTimeByScalingStep;
+ private long[] mTempUidStats;
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
- private final int mUidStatsSize;
+ private boolean mIsPerUidTimeInStateSupported;
+ private PowerStatsInternal mPowerStatsInternal;
+ private int[] mCpuEnergyConsumerIds;
+ private PowerStats.Descriptor mPowerStatsDescriptor;
// Reusable instance
- private final PowerStats mCpuPowerStats;
+ private PowerStats mCpuPowerStats;
+ private StatsArrayLayout mLayout;
private long mLastUpdateTimestampNanos;
+ private long mLastUpdateUptimeMillis;
+ private int mLastVoltageMv;
+ private long[] mLastConsumedEnergyUws;
+
+ /**
+ * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+ * power usage estimates etc.
+ */
+ public static class StatsArrayLayout {
+ private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt";
+ private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc";
+ private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc";
+ private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc";
+ private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
+ private static final String EXTRA_DEVICE_UPTIME_POSITION = "du";
+ private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
+ private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
+ private static final String EXTRA_UID_BRACKETS_POSITION = "ub";
+ private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us";
+ private static final String EXTRA_UID_POWER_POSITION = "up";
+
+ private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+
+ private int mDeviceStatsArrayLength;
+ private int mUidStatsArrayLength;
+
+ private int mDeviceCpuTimeByScalingStepPosition;
+ private int mDeviceCpuTimeByScalingStepCount;
+ private int mDeviceCpuTimeByClusterPosition;
+ private int mDeviceCpuTimeByClusterCount;
+ private int mDeviceCpuUptimePosition;
+ private int mDeviceEnergyConsumerPosition;
+ private int mDeviceEnergyConsumerCount;
+ private int mDevicePowerEstimatePosition;
+
+ private int mUidPowerBracketsPosition;
+ private int mUidPowerBracketCount;
+ private int[][] mEnergyConsumerToPowerBucketMaps;
+ private int mUidPowerEstimatePosition;
+
+ private int[] mScalingStepToPowerBracketMap;
+
+ public int getDeviceStatsArrayLength() {
+ return mDeviceStatsArrayLength;
+ }
+
+ public int getUidStatsArrayLength() {
+ return mUidStatsArrayLength;
+ }
+
+ /**
+ * Declare that the stats array has a section capturing CPU time per scaling step
+ */
+ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
+ mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength;
+ mDeviceCpuTimeByScalingStepCount = scalingStepCount;
+ mDeviceStatsArrayLength += scalingStepCount;
+ }
+
+ public int getCpuScalingStepCount() {
+ return mDeviceCpuTimeByScalingStepCount;
+ }
+
+ /**
+ * Saves the time duration in the <code>stats</code> element
+ * corresponding to the CPU scaling <code>state</code>.
+ */
+ public void setTimeByScalingStep(long[] stats, int step, long value) {
+ stats[mDeviceCpuTimeByScalingStepPosition + step] = value;
+ }
+
+ /**
+ * Extracts the time duration from the <code>stats</code> element
+ * corresponding to the CPU scaling <code>step</code>.
+ */
+ public long getTimeByScalingStep(long[] stats, int step) {
+ return stats[mDeviceCpuTimeByScalingStepPosition + step];
+ }
+
+ /**
+ * Declare that the stats array has a section capturing CPU time in each cluster
+ */
+ public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
+ mDeviceCpuTimeByClusterCount = clusterCount;
+ mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength;
+ mDeviceStatsArrayLength += clusterCount;
+ }
+
+ public int getCpuClusterCount() {
+ return mDeviceCpuTimeByClusterCount;
+ }
+
+ /**
+ * Saves the time duration in the <code>stats</code> element
+ * corresponding to the CPU <code>cluster</code>.
+ */
+ public void setTimeByCluster(long[] stats, int cluster, long value) {
+ stats[mDeviceCpuTimeByClusterPosition + cluster] = value;
+ }
+
+ /**
+ * Extracts the time duration from the <code>stats</code> element
+ * corresponding to the CPU <code>cluster</code>.
+ */
+ public long getTimeByCluster(long[] stats, int cluster) {
+ return stats[mDeviceCpuTimeByClusterPosition + cluster];
+ }
+
+ /**
+ * Declare that the stats array has a section capturing CPU uptime
+ */
+ public void addDeviceSectionUptime() {
+ mDeviceCpuUptimePosition = mDeviceStatsArrayLength++;
+ }
+
+ /**
+ * Saves the CPU uptime duration in the corresponding <code>stats</code> element.
+ */
+ public void setUptime(long[] stats, long value) {
+ stats[mDeviceCpuUptimePosition] = value;
+ }
+
+ /**
+ * Extracts the CPU uptime duration from the corresponding <code>stats</code> element.
+ */
+ public long getUptime(long[] stats) {
+ return stats[mDeviceCpuUptimePosition];
+ }
+
+ /**
+ * Declares that the stats array has a section capturing EnergyConsumer data from
+ * PowerStatsService.
+ */
+ public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
+ mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength;
+ mDeviceEnergyConsumerCount = energyConsumerCount;
+ mDeviceStatsArrayLength += energyConsumerCount;
+ }
+
+ public int getCpuClusterEnergyConsumerCount() {
+ return mDeviceEnergyConsumerCount;
+ }
+
+ /**
+ * Saves the accumulated energy for the specified rail the corresponding
+ * <code>stats</code> element.
+ */
+ public void setConsumedEnergy(long[] stats, int index, long energy) {
+ stats[mDeviceEnergyConsumerPosition + index] = energy;
+ }
+
+ /**
+ * Extracts the EnergyConsumer data from a device stats array for the specified
+ * EnergyConsumer.
+ */
+ public long getConsumedEnergy(long[] stats, int index) {
+ return stats[mDeviceEnergyConsumerPosition + index];
+ }
+
+ /**
+ * Declare that the stats array has a section capturing a power estimate
+ */
+ public void addDeviceSectionPowerEstimate() {
+ mDevicePowerEstimatePosition = mDeviceStatsArrayLength++;
+ }
+
+ /**
+ * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+ * element of <code>stats</code>.
+ */
+ public void setDevicePowerEstimate(long[] stats, double power) {
+ stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Extracts the power estimate from a device stats array and converts it to mAh.
+ */
+ public double getDevicePowerEstimate(long[] stats) {
+ return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ /**
+ * Declare that the UID stats array has a section capturing CPU time per power bracket.
+ */
+ public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
+ mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
+ mUidPowerBracketsPosition = mUidStatsArrayLength;
+ updatePowerBracketCount();
+ mUidStatsArrayLength += mUidPowerBracketCount;
+ }
+
+ private void updatePowerBracketCount() {
+ mUidPowerBracketCount = 1;
+ for (int bracket : mScalingStepToPowerBracketMap) {
+ if (bracket >= mUidPowerBracketCount) {
+ mUidPowerBracketCount = bracket + 1;
+ }
+ }
+ }
+
+ public int[] getScalingStepToPowerBracketMap() {
+ return mScalingStepToPowerBracketMap;
+ }
+
+ public int getCpuPowerBracketCount() {
+ return mUidPowerBracketCount;
+ }
+
+ /**
+ * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>.
+ */
+ public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) {
+ stats[mUidPowerBracketsPosition + bracket] = value;
+ }
+
+ /**
+ * Extracts the time in <code>bracket</code> from a UID stats array.
+ */
+ public long getUidTimeByPowerBracket(long[] stats, int bracket) {
+ return stats[mUidPowerBracketsPosition + bracket];
+ }
+
+ /**
+ * Declare that the UID stats array has a section capturing a power estimate
+ */
+ public void addUidSectionPowerEstimate() {
+ mUidPowerEstimatePosition = mUidStatsArrayLength++;
+ }
+
+ /**
+ * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+ * element of <code>stats</code>.
+ */
+ public void setUidPowerEstimate(long[] stats, double power) {
+ stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Extracts the power estimate from a UID stats array and converts it to mAh.
+ */
+ public double getUidPowerEstimate(long[] stats) {
+ return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ /**
+ * Copies the elements of the stats array layout into <code>extras</code>
+ */
+ public void toExtras(PersistableBundle extras) {
+ extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
+ mDeviceCpuTimeByScalingStepPosition);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
+ mDeviceCpuTimeByScalingStepCount);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
+ mDeviceCpuTimeByClusterPosition);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
+ mDeviceCpuTimeByClusterCount);
+ extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition);
+ extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
+ mDeviceEnergyConsumerPosition);
+ extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
+ mDeviceEnergyConsumerCount);
+ extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+ extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
+ extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
+ mScalingStepToPowerBracketMap);
+ extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+ }
+
+ /**
+ * Retrieves elements of the stats array layout from <code>extras</code>
+ */
+ public void fromExtras(PersistableBundle extras) {
+ mDeviceCpuTimeByScalingStepPosition =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
+ mDeviceCpuTimeByScalingStepCount =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
+ mDeviceCpuTimeByClusterPosition =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
+ mDeviceCpuTimeByClusterCount =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
+ mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION);
+ mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
+ mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
+ mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+ mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
+ mScalingStepToPowerBracketMap =
+ extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
+ if (mScalingStepToPowerBracketMap == null) {
+ mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
+ }
+ updatePowerBracketCount();
+ mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
+ }
+ }
public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
- Handler handler, long throttlePeriodMs) {
+ IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) {
this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(),
- throttlePeriodMs, Clock.SYSTEM_CLOCK);
+ () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier,
+ throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS,
+ DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER);
}
public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
Handler handler, KernelCpuStatsReader kernelCpuStatsReader,
- long throttlePeriodMs, Clock clock) {
+ Supplier<PowerStatsInternal> powerStatsSupplier,
+ IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock,
+ int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) {
super(handler, throttlePeriodMs, clock);
+ mCpuScalingPolicies = cpuScalingPolicies;
+ mPowerProfile = powerProfile;
mKernelCpuStatsReader = kernelCpuStatsReader;
+ mPowerStatsSupplier = powerStatsSupplier;
+ mVoltageSupplier = voltageSupplier;
+ mDefaultCpuPowerBrackets = defaultCpuPowerBrackets;
+ mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
- int scalingStepCount = cpuScalingPolicies.getScalingStepCount();
- mScalingStepToPowerBracketMap = new int[scalingStepCount];
- int index = 0;
- for (int policy : cpuScalingPolicies.getPolicies()) {
- int[] frequencies = cpuScalingPolicies.getFrequencies(policy);
- for (int step = 0; step < frequencies.length; step++) {
- int bracket = powerProfile.getCpuPowerBracketForScalingStep(policy, step);
- mScalingStepToPowerBracketMap[index++] = bracket;
- }
- }
- mUidStatsSize = powerProfile.getCpuPowerBracketCount();
- mTempUidStats = new long[mUidStatsSize];
-
- mCpuPowerStats = new PowerStats(
- new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
- new PersistableBundle()));
}
/**
@@ -84,43 +409,355 @@
setEnabled(Flags.streamlinedBatteryStats());
}
+ private void ensureInitialized() {
+ if (mIsInitialized) {
+ return;
+ }
+
+ if (!isEnabled()) {
+ return;
+ }
+
+ mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature();
+ mPowerStatsInternal = mPowerStatsSupplier.get();
+
+ if (mPowerStatsInternal != null) {
+ readCpuEnergyConsumerIds();
+ } else {
+ mCpuEnergyConsumerIds = new int[0];
+ }
+
+ int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
+ mCpuTimeByScalingStep = new long[cpuScalingStepCount];
+ mTempCpuTimeByScalingStep = new long[cpuScalingStepCount];
+ int[] scalingStepToPowerBracketMap = initPowerBrackets();
+
+ mLayout = new StatsArrayLayout();
+ mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount);
+ mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length);
+ mLayout.addDeviceSectionUptime();
+ mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length);
+ mLayout.addDeviceSectionPowerEstimate();
+ mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap);
+ mLayout.addUidSectionPowerEstimate();
+
+ PersistableBundle extras = new PersistableBundle();
+ mLayout.toExtras(extras);
+
+ mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
+ mLayout.getDeviceStatsArrayLength(), mLayout.getUidStatsArrayLength(), extras);
+ mCpuPowerStats = new PowerStats(mPowerStatsDescriptor);
+
+ mTempUidStats = new long[mLayout.getCpuPowerBracketCount()];
+
+ mIsInitialized = true;
+ }
+
+ private void readCpuEnergyConsumerIds() {
+ EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
+ if (energyConsumerInfo == null) {
+ mCpuEnergyConsumerIds = new int[0];
+ return;
+ }
+
+ List<EnergyConsumer> cpuEnergyConsumers = new ArrayList<>();
+ for (EnergyConsumer energyConsumer : energyConsumerInfo) {
+ if (energyConsumer.type == EnergyConsumerType.CPU_CLUSTER) {
+ cpuEnergyConsumers.add(energyConsumer);
+ }
+ }
+ if (cpuEnergyConsumers.isEmpty()) {
+ return;
+ }
+
+ cpuEnergyConsumers.sort(Comparator.comparing(c -> c.ordinal));
+
+ mCpuEnergyConsumerIds = new int[cpuEnergyConsumers.size()];
+ for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
+ mCpuEnergyConsumerIds[i] = cpuEnergyConsumers.get(i).id;
+ }
+ mLastConsumedEnergyUws = new long[cpuEnergyConsumers.size()];
+ Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+ }
+
+ private int[] initPowerBrackets() {
+ if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) {
+ return initPowerBracketsFromPowerProfile();
+ } else if (mCpuEnergyConsumerIds.length == 0 || mCpuEnergyConsumerIds.length == 1) {
+ return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
+ } else if (mCpuScalingPolicies.getPolicies().length == mCpuEnergyConsumerIds.length) {
+ return initPowerBracketsByCluster(mDefaultCpuPowerBracketsPerEnergyConsumer);
+ } else {
+ Slog.i(TAG, "Assigning a single power brackets to each CPU_CLUSTER energy consumer."
+ + " Number of CPU clusters ("
+ + mCpuScalingPolicies.getPolicies().length
+ + ") does not match the number of energy consumers ("
+ + mCpuEnergyConsumerIds.length + "). "
+ + " Using default power bucket assignment.");
+ return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
+ }
+ }
+
+ private int[] initPowerBracketsFromPowerProfile() {
+ int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+ int index = 0;
+ for (int policy : mCpuScalingPolicies.getPolicies()) {
+ int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+ for (int step = 0; step < frequencies.length; step++) {
+ int bracket = mPowerProfile.getCpuPowerBracketForScalingStep(policy, step);
+ stepToBracketMap[index++] = bracket;
+ }
+ }
+ return stepToBracketMap;
+ }
+
+ private int[] initPowerBracketsByCluster(int defaultBracketCountPerCluster) {
+ int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+ int index = 0;
+ int bracketBase = 0;
+ int[] policies = mCpuScalingPolicies.getPolicies();
+ for (int policy : policies) {
+ int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+ double[] powerByStep = new double[frequencies.length];
+ for (int step = 0; step < frequencies.length; step++) {
+ powerByStep[step] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step);
+ }
+
+ int[] policyStepToBracketMap = new int[frequencies.length];
+ mapScalingStepsToDefaultBrackets(policyStepToBracketMap, powerByStep,
+ defaultBracketCountPerCluster);
+ int maxBracket = 0;
+ for (int step = 0; step < frequencies.length; step++) {
+ int bracket = bracketBase + policyStepToBracketMap[step];
+ stepToBracketMap[index++] = bracket;
+ if (bracket > maxBracket) {
+ maxBracket = bracket;
+ }
+ }
+ bracketBase = maxBracket + 1;
+ }
+ return stepToBracketMap;
+ }
+
+ private int[] initDefaultPowerBrackets(int defaultCpuPowerBracketCount) {
+ int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
+ double[] powerByStep = new double[mCpuScalingPolicies.getScalingStepCount()];
+ int index = 0;
+ int[] policies = mCpuScalingPolicies.getPolicies();
+ for (int policy : policies) {
+ int[] frequencies = mCpuScalingPolicies.getFrequencies(policy);
+ for (int step = 0; step < frequencies.length; step++) {
+ powerByStep[index++] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step);
+ }
+ }
+ mapScalingStepsToDefaultBrackets(stepToBracketMap, powerByStep,
+ defaultCpuPowerBracketCount);
+ return stepToBracketMap;
+ }
+
+ private static void mapScalingStepsToDefaultBrackets(int[] stepToBracketMap,
+ double[] powerByStep, int defaultCpuPowerBracketCount) {
+ double minPower = Double.MAX_VALUE;
+ double maxPower = Double.MIN_VALUE;
+ for (final double power : powerByStep) {
+ if (power < minPower) {
+ minPower = power;
+ }
+ if (power > maxPower) {
+ maxPower = power;
+ }
+ }
+ if (powerByStep.length <= defaultCpuPowerBracketCount) {
+ for (int index = 0; index < stepToBracketMap.length; index++) {
+ stepToBracketMap[index] = index;
+ }
+ } else {
+ final double minLogPower = Math.log(minPower);
+ final double logBracket = (Math.log(maxPower) - minLogPower)
+ / defaultCpuPowerBracketCount;
+
+ for (int step = 0; step < powerByStep.length; step++) {
+ int bracket = (int) ((Math.log(powerByStep[step]) - minLogPower) / logBracket);
+ if (bracket >= defaultCpuPowerBracketCount) {
+ bracket = defaultCpuPowerBracketCount - 1;
+ }
+ stepToBracketMap[step] = bracket;
+ }
+ }
+ }
+
+ /**
+ * Prints the definitions of power brackets.
+ */
+ public void dumpCpuPowerBracketsLocked(PrintWriter pw) {
+ ensureInitialized();
+
+ pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):");
+ for (int bracket = 0; bracket < mLayout.getCpuPowerBracketCount(); bracket++) {
+ pw.print(" ");
+ pw.print(bracket);
+ pw.print(": ");
+ pw.println(getCpuPowerBracketDescription(bracket));
+ }
+ }
+
+ /**
+ * Description of a CPU power bracket: which cluster/frequency combinations are included.
+ */
+ @VisibleForTesting
+ public String getCpuPowerBracketDescription(int powerBracket) {
+ ensureInitialized();
+
+ int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap();
+ StringBuilder sb = new StringBuilder();
+ int index = 0;
+ int[] policies = mCpuScalingPolicies.getPolicies();
+ for (int policy : policies) {
+ int[] freqs = mCpuScalingPolicies.getFrequencies(policy);
+ for (int step = 0; step < freqs.length; step++) {
+ if (stepToPowerBracketMap[index] != powerBracket) {
+ index++;
+ continue;
+ }
+
+ if (sb.length() != 0) {
+ sb.append(", ");
+ }
+ if (policies.length > 1) {
+ sb.append(policy).append('/');
+ }
+ sb.append(freqs[step] / 1000);
+ sb.append('(');
+ sb.append(String.format(Locale.US, "%.1f",
+ mPowerProfile.getAveragePowerForCpuScalingStep(policy, step)));
+ sb.append(')');
+
+ index++;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the descriptor of PowerStats produced by this collector.
+ */
+ @VisibleForTesting
+ public PowerStats.Descriptor getPowerStatsDescriptor() {
+ ensureInitialized();
+
+ return mPowerStatsDescriptor;
+ }
+
@Override
protected PowerStats collectStats() {
+ ensureInitialized();
+
+ if (!mIsPerUidTimeInStateSupported) {
+ return null;
+ }
+
mCpuPowerStats.uidStats.clear();
- long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(
- this::processUidStats, mScalingStepToPowerBracketMap, mLastUpdateTimestampNanos,
- mTempUidStats);
+ // TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster
+ long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(this::processUidStats,
+ mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos,
+ mTempCpuTimeByScalingStep, mTempUidStats);
+ for (int step = mLayout.getCpuScalingStepCount() - 1; step >= 0; step--) {
+ mLayout.setTimeByScalingStep(mCpuPowerStats.stats, step,
+ mTempCpuTimeByScalingStep[step] - mCpuTimeByScalingStep[step]);
+ mCpuTimeByScalingStep[step] = mTempCpuTimeByScalingStep[step];
+ }
+
mCpuPowerStats.durationMs =
(newTimestampNanos - mLastUpdateTimestampNanos) / NANOS_PER_MILLIS;
mLastUpdateTimestampNanos = newTimestampNanos;
+
+ long uptimeMillis = mClock.uptimeMillis();
+ long uptimeDelta = uptimeMillis - mLastUpdateUptimeMillis;
+ mLastUpdateUptimeMillis = uptimeMillis;
+
+ if (uptimeDelta > mCpuPowerStats.durationMs) {
+ uptimeDelta = mCpuPowerStats.durationMs;
+ }
+ mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta);
+
+ if (mCpuEnergyConsumerIds.length != 0) {
+ collectEnergyConsumers();
+ }
+
return mCpuPowerStats;
}
+ private void collectEnergyConsumers() {
+ int voltageMv = mVoltageSupplier.getAsInt();
+ if (voltageMv <= 0) {
+ Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+ + " mV) when querying energy consumers");
+ return;
+ }
+
+ int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+ mLastVoltageMv = voltageMv;
+
+ CompletableFuture<EnergyConsumerResult[]> future =
+ mPowerStatsInternal.getEnergyConsumedAsync(mCpuEnergyConsumerIds);
+ EnergyConsumerResult[] results = null;
+ try {
+ results = future.get(
+ POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
+ }
+ if (results == null) {
+ return;
+ }
+
+ for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
+ int id = mCpuEnergyConsumerIds[i];
+ for (EnergyConsumerResult result : results) {
+ if (result.id == id) {
+ long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+ ? result.energyUWs - mLastConsumedEnergyUws[i] : 0;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
+ }
+ mLayout.setConsumedEnergy(mCpuPowerStats.stats, i,
+ uJtoUc(energyDelta, averageVoltage));
+ mLastConsumedEnergyUws[i] = result.energyUWs;
+ break;
+ }
+ }
+ }
+ }
+
@VisibleForNative
interface KernelCpuStatsCallback {
@Keep // Called from native
- void processUidStats(int uid, long[] stats);
+ void processUidStats(int uid, long[] timeByPowerBracket);
}
- private void processUidStats(int uid, long[] stats) {
+ private void processUidStats(int uid, long[] timeByPowerBracket) {
+ int powerBracketCount = mLayout.getCpuPowerBracketCount();
+
UidStats uidStats = mUidStats.get(uid);
if (uidStats == null) {
uidStats = new UidStats();
- uidStats.stats = new long[mUidStatsSize];
- uidStats.delta = new long[mUidStatsSize];
+ uidStats.timeByPowerBracket = new long[powerBracketCount];
+ uidStats.stats = new long[mLayout.getUidStatsArrayLength()];
mUidStats.put(uid, uidStats);
}
boolean nonzero = false;
- for (int i = mUidStatsSize - 1; i >= 0; i--) {
- long delta = uidStats.delta[i] = stats[i] - uidStats.stats[i];
+ for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
+ long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
if (delta != 0) {
nonzero = true;
}
- uidStats.stats[i] = stats[i];
+ mLayout.setUidTimeByPowerBracket(uidStats.stats, bracket, delta);
+ uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket];
}
if (nonzero) {
- mCpuPowerStats.uidStats.put(uid, uidStats.delta);
+ mCpuPowerStats.uidStats.put(uid, uidStats.stats);
}
}
@@ -128,13 +765,15 @@
* Native class that retrieves CPU stats from the kernel.
*/
public static class KernelCpuStatsReader {
+ protected native boolean nativeIsSupportedFeature();
+
protected native long nativeReadCpuStats(KernelCpuStatsCallback callback,
int[] scalingStepToPowerBracketMap, long lastUpdateTimestampNanos,
- long[] tempForUidStats);
+ long[] outCpuTimeByScalingStep, long[] tempForUidStats);
}
private static class UidStats {
public long[] stats;
- public long[] delta;
+ public long[] timeByPowerBracket;
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 05c0a13..2c7843e 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -16,6 +16,8 @@
package com.android.server.power.stats;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
@@ -46,6 +48,8 @@
public final int powerComponentId;
private final MultiStateStats.States[] mDeviceStateConfig;
private final MultiStateStats.States[] mUidStateConfig;
+ @NonNull
+ private final AggregatedPowerStatsConfig.PowerComponent mConfig;
private final int[] mDeviceStates;
private final long[] mDeviceStateTimestamps;
@@ -62,13 +66,20 @@
}
PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
- this.powerComponentId = config.getPowerComponentId();
+ mConfig = config;
+ powerComponentId = config.getPowerComponentId();
mDeviceStateConfig = config.getDeviceStateConfig();
mUidStateConfig = config.getUidStateConfig();
mDeviceStates = new int[mDeviceStateConfig.length];
mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
}
+ @NonNull
+ public AggregatedPowerStatsConfig.PowerComponent getConfig() {
+ return mConfig;
+ }
+
+ @Nullable
public PowerStats.Descriptor getPowerStatsDescriptor() {
return mPowerStatsDescriptor;
}
@@ -108,6 +119,16 @@
}
}
+ void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) {
+ mDeviceStats.setStats(states, values);
+ }
+
+ void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states,
+ long[] values) {
+ UidStats uidStats = getUidStats(uid);
+ uidStats.stats.setStats(states, values);
+ }
+
boolean isCompatible(PowerStats powerStats) {
return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor);
}
@@ -298,7 +319,8 @@
if (mDeviceStats != null) {
ipw.println(mPowerStatsDescriptor.name);
ipw.increaseIndent();
- mDeviceStats.dump(ipw);
+ mDeviceStats.dump(ipw, stats ->
+ mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
ipw.decreaseIndent();
}
}
@@ -308,7 +330,8 @@
if (uidStats != null && uidStats.stats != null) {
ipw.println(mPowerStatsDescriptor.name);
ipw.increaseIndent();
- uidStats.stats.dump(ipw);
+ uidStats.stats.dump(ipw, stats ->
+ mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats));
ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index f374fb7..2f9d567 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -16,6 +16,7 @@
package com.android.server.power.stats;
import android.os.BatteryStats;
+import android.util.SparseArray;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
@@ -30,11 +31,17 @@
public class PowerStatsAggregator {
private final AggregatedPowerStats mStats;
private final BatteryStatsHistory mHistory;
+ private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>();
public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
BatteryStatsHistory history) {
mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
mHistory = history;
+ for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig :
+ aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) {
+ AggregatedPowerStatsProcessor processor = powerComponentsConfig.getProcessor();
+ mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor);
+ }
}
/**
@@ -100,6 +107,7 @@
if (!mStats.isCompatible(item.powerStats)) {
if (lastTime > baseTime) {
mStats.setDuration(lastTime - baseTime);
+ finish(mStats);
consumer.accept(mStats);
}
mStats.reset();
@@ -112,9 +120,20 @@
}
if (lastTime > baseTime) {
mStats.setDuration(lastTime - baseTime);
+ finish(mStats);
consumer.accept(mStats);
}
mStats.reset(); // to free up memory
}
+
+ private void finish(AggregatedPowerStats stats) {
+ for (int i = 0; i < mProcessors.size(); i++) {
+ PowerComponentAggregatedPowerStats component =
+ stats.getPowerComponentStats(mProcessors.keyAt(i));
+ if (component != null) {
+ mProcessors.valueAt(i).finish(component);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index b49c89f..84cc21e 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -38,8 +38,9 @@
* except where noted.
*/
public abstract class PowerStatsCollector {
+ private static final int MILLIVOLTS_PER_VOLT = 1000;
private final Handler mHandler;
- private final Clock mClock;
+ protected final Clock mClock;
private final long mThrottlePeriodMs;
private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
private boolean mEnabled;
@@ -100,6 +101,9 @@
@SuppressWarnings("GuardedBy") // Field is volatile
private void collectAndDeliverStats() {
PowerStats stats = collectStats();
+ if (stats == null) {
+ return;
+ }
for (Consumer<PowerStats> consumer : mConsumerList) {
consumer.accept(stats);
}
@@ -180,4 +184,11 @@
mHandler.post(done::open);
done.block();
}
+
+ /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
+ protected long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
+ // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
+ // since the last snapshot. Round off to the nearest whole long.
+ return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
index 58619c7..551302e 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -108,6 +108,9 @@
long currentTimeMillis = mClock.currentTimeMillis();
long currentMonotonicTime = mMonotonicClock.monotonicTime();
long startTime = getLastSavedSpanEndMonotonicTime();
+ if (startTime < 0) {
+ startTime = mBatteryStats.getHistory().getStartTime();
+ }
long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
while (endTimeMs <= currentMonotonicTime) {
@@ -214,6 +217,7 @@
return mLastSavedSpanEndMonotonicTime;
}
+ mLastSavedSpanEndMonotonicTime = -1;
for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index 7b0fe9a..a01bac6 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -266,10 +266,10 @@
}
/**
- * @return {@code true} iff. {@code userId} is locked on an FBE device.
+ * @return {@code true} iff the credential-encrypted storage for {@code userId} is locked.
*/
@VisibleForTesting
public boolean isUserCredentialLocked(int userId) {
- return StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId);
+ return StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId);
}
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 85d93f4..a5b90f1 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -174,6 +174,11 @@
@Nullable private final String mInstallerPackageName;
/**
+ * Time after which rollback expires.
+ */
+ private long mRollbackLifetimeMillis = 0;
+
+ /**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
@@ -286,6 +291,24 @@
}
/**
+ * Sets rollback lifetime in milliseconds, for purposes of expiring rollback data.
+ */
+ @WorkerThread
+ void setRollbackLifetimeMillis(long lifetimeMillis) {
+ assertInWorkerThread();
+ mRollbackLifetimeMillis = lifetimeMillis;
+ }
+
+ /**
+ * Returns rollback lifetime in milliseconds, for purposes of expiring rollback data.
+ */
+ @WorkerThread
+ long getRollbackLifetimeMillis() {
+ assertInWorkerThread();
+ return mRollbackLifetimeMillis;
+ }
+
+ /**
* Returns the session ID associated with this rollback, or {@code -1} if unknown.
*/
@AnyThread
@@ -930,6 +953,7 @@
ipw.println("-state: " + getStateAsString());
ipw.println("-stateDescription: " + mStateDescription);
ipw.println("-timestamp: " + getTimestamp());
+ ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
ipw.println("-isStaged: " + isStaged());
ipw.println("-originalSessionId: " + getOriginalSessionId());
ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 720c773..8d93408 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -702,6 +703,15 @@
// Schedules future expiration as appropriate.
@WorkerThread
private void runExpiration() {
+ if (Flags.rollbackLifetime()) {
+ runExpirationCustomRollbackLifetime();
+ } else {
+ runExpirationDefaultRollbackLifetime();
+ }
+ }
+
+ @WorkerThread
+ private void runExpirationDefaultRollbackLifetime() {
getHandler().removeCallbacks(mRunExpiration);
assertInWorkerThread();
Instant now = Instant.now();
@@ -729,6 +739,44 @@
}
}
+ @WorkerThread
+ private void runExpirationCustomRollbackLifetime() {
+ getHandler().removeCallbacks(mRunExpiration);
+ assertInWorkerThread();
+ Instant now = Instant.now();
+ long minDelay = 0;
+ Iterator<Rollback> iter = mRollbacks.iterator();
+ while (iter.hasNext()) {
+ Rollback rollback = iter.next();
+ if (!rollback.isAvailable() && !rollback.isCommitted()) {
+ continue;
+ }
+ long rollbackLifetimeMillis = rollback.getRollbackLifetimeMillis();
+ if (rollbackLifetimeMillis <= 0) {
+ rollbackLifetimeMillis = mRollbackLifetimeDurationInMillis;
+ }
+
+ Instant rollbackExpiryTimestamp = rollback.getTimestamp()
+ .plusMillis(rollbackLifetimeMillis);
+ if (!now.isBefore(rollbackExpiryTimestamp)) {
+ Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId());
+ iter.remove();
+ deleteRollback(rollback, "Expired by timeout");
+ continue;
+ }
+
+ long delay = now.until(
+ rollbackExpiryTimestamp, ChronoUnit.MILLIS);
+ if (minDelay == 0 || delay < minDelay) {
+ minDelay = delay;
+ }
+ }
+
+ if (minDelay != 0) {
+ getHandler().postDelayed(mRunExpiration, minDelay);
+ }
+ }
+
@AnyThread
private Handler getHandler() {
return mHandler;
@@ -1277,6 +1325,7 @@
}
final Rollback rollback;
+
if (parentSession.isStaged()) {
rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId,
installerPackageName, packageSessionIds, getExtensionVersions());
@@ -1285,6 +1334,11 @@
installerPackageName, packageSessionIds, getExtensionVersions());
}
+ if (Flags.rollbackLifetime()) {
+ rollback.setRollbackLifetimeMillis(parentSession.rollbackLifetimeMillis);
+ }
+
+
mRollbacks.add(rollback);
return rollback;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 8068c6f..0af137f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -19,6 +19,7 @@
import static com.android.server.rollback.Rollback.rollbackStateFromString;
import android.annotation.NonNull;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -312,6 +313,9 @@
JSONObject dataJson = new JSONObject();
dataJson.put("info", rollbackInfoToJson(rollback.info));
dataJson.put("timestamp", rollback.getTimestamp().toString());
+ if (Flags.rollbackLifetime()) {
+ dataJson.put("rollbackLifetimeMillis", rollback.getRollbackLifetimeMillis());
+ }
dataJson.put("originalSessionId", rollback.getOriginalSessionId());
dataJson.put("state", rollback.getStateAsString());
dataJson.put("stateDescription", rollback.getStateDescription());
@@ -375,7 +379,7 @@
@VisibleForTesting
static Rollback rollbackFromJson(JSONObject dataJson, File backupDir)
throws JSONException, ParseException {
- return new Rollback(
+ Rollback rollback = new Rollback(
rollbackInfoFromJson(dataJson.getJSONObject("info")),
backupDir,
Instant.parse(dataJson.getString("timestamp")),
@@ -388,6 +392,10 @@
dataJson.optInt("userId", UserHandle.SYSTEM.getIdentifier()),
dataJson.optString("installerPackageName", ""),
extensionVersionsFromJson(dataJson.optJSONArray("extensionVersions")));
+ if (Flags.rollbackLifetime()) {
+ rollback.setRollbackLifetimeMillis(dataJson.optLong("rollbackLifetimeMillis"));
+ }
+ return rollback;
}
private static JSONObject toJson(VersionedPackage pkg) throws JSONException {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 635e11b..f82f08b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -70,7 +70,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
-import android.view.Display;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
@@ -79,7 +78,6 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -1523,57 +1521,13 @@
mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget();
}
- /**
- * @param uid: uid of the calling app (obtained via getCallingUid())
- * @param displayId: the id of a Display
- * @return Returns true if both of the following conditions hold -
- * 1) the uid belongs to an app instead of a system core component; and
- * 2) either the uid is running on a virtual device or the displayId
- * is owned by a virtual device
- */
- private boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) {
- if (UserHandle.isCore(uid)) {
- return false;
- }
-
- if (mVirtualDeviceManager == null) {
- mVirtualDeviceManager = LocalServices.getService(
- VirtualDeviceManagerInternal.class);
- if (mVirtualDeviceManager == null) {
- // VirtualDeviceManager service may not have been published
- return false;
- }
- }
-
- switch (displayId) {
- case Display.INVALID_DISPLAY:
- // There is no Display object associated with the Context of the calling app.
- if (mVirtualDeviceManager.isAppRunningOnAnyVirtualDevice(uid)) {
- return true;
- }
- break;
- case Display.DEFAULT_DISPLAY:
- // The DEFAULT_DISPLAY is by definition not virtual.
- break;
- default:
- // Other display IDs can belong to logical displays created for other purposes.
- if (mVirtualDeviceManager.isDisplayOwnedByAnyVirtualDevice(displayId)) {
- return true;
- }
- break;
- }
- return false;
- }
-
@Override
- public boolean isDeviceLocked(int userId, int displayId) throws RemoteException {
- int uid = getCallingUid();
- if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) {
- // Virtual displays are considered insecure because they may be used for streaming
- // to other devices.
+ public boolean isDeviceLocked(int userId, int deviceId) throws RemoteException {
+ if (deviceId != Context.DEVICE_ID_DEFAULT) {
+ // Virtual devices are considered insecure.
return false;
}
- userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId,
+ userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceLocked", null);
final long token = Binder.clearCallingIdentity();
@@ -1588,15 +1542,12 @@
}
@Override
- public boolean isDeviceSecure(int userId, int displayId) throws RemoteException {
- int uid = getCallingUid();
- if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) {
- // Virtual displays are considered insecure because they may be used for streaming
- // to other devices.
+ public boolean isDeviceSecure(int userId, int deviceId) throws RemoteException {
+ if (deviceId != Context.DEVICE_ID_DEFAULT) {
+ // Virtual devices are considered insecure.
return false;
}
-
- userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId,
+ userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceSecure", null);
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9fa5ed2..bdab4d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3055,7 +3055,7 @@
@Override
boolean providesOrientation() {
- return mStyleFillsParent;
+ return mStyleFillsParent || mOccludesParent;
}
@Override
@@ -9317,8 +9317,9 @@
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 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 @@
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/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 01b8bf7..52ab9b8 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -539,7 +539,7 @@
if (usf != null) {
mUserSavedFiles.get(userId).remove(code);
mSavedFilesInOrder.remove(usf);
- mPersister.removeSnap(code, userId);
+ mPersister.removeSnapshot(code, userId);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 7cccf6b..34bf8ed 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1097,23 +1097,22 @@
"shouldAbortBackgroundActivityStart");
BackgroundActivityStartController balController =
mSupervisor.getBackgroundActivityLaunchController();
- balCode =
+ BackgroundActivityStartController.BalVerdict balVerdict =
balController.checkBackgroundActivityStart(
- callingUid,
- callingPid,
- callingPackage,
- realCallingUid,
- realCallingPid,
- callerApp,
- request.originatingPendingIntent,
- request.backgroundStartPrivileges,
- intent,
- checkedOptions);
- if (balCode != BAL_ALLOW_DEFAULT) {
- request.logMessage.append(" (").append(
- BackgroundActivityStartController.balCodeToString(balCode))
- .append(")");
- }
+ callingUid,
+ callingPid,
+ callingPackage,
+ realCallingUid,
+ realCallingPid,
+ callerApp,
+ request.originatingPendingIntent,
+ request.backgroundStartPrivileges,
+ intent,
+ checkedOptions);
+ balCode = balVerdict.getCode();
+ request.logMessage.append(" (").append(
+ BackgroundActivityStartController.balCodeToString(balCode))
+ .append(")");
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index e97bda2..9b7b8de 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.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.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 @@
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
@@ -202,15 +204,181 @@
return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
realCallingUid, realCallingPid,
callerApp, originatingPendingIntent,
- backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
+ backgroundStartPrivileges, intent, checkedOptions).blocks();
+ }
+
+ 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(BalVerdict resultIfPiCreatorAllowsBal,
+ BalVerdict resultIfPiSenderAllowsBal) {
+ 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: " + resultIfPiSenderAllowsBal
+ + "; resultIfPiCreatorAllowsBal: " + resultIfPiCreatorAllowsBal
+ + "]";
+ }
+ }
+
+ static class BalVerdict {
+
+ static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+ private final @BalCode int mCode;
+ private final boolean mBackground;
+ private final String mMessage;
+ private String mProcessInfo;
+
+ BalVerdict(@BalCode int balCode, boolean background, String message) {
+ this.mBackground = background;
+ this.mCode = balCode;
+ this.mMessage = message;
+ }
+
+ public BalVerdict withProcessInfo(String msg, WindowProcessController process) {
+ mProcessInfo = msg + " (uid=" + process.mUid + ",pid=" + process.getPid() + ")";
+ return this;
+ }
+
+ boolean blocks() {
+ return mCode == BAL_BLOCK;
+ }
+
+ boolean allows() {
+ return !blocks();
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (mBackground) {
+ builder.append("Background ");
+ }
+ builder.append("Activity start allowed: " + mMessage + ".");
+ builder.append("BAL Code: ");
+ builder.append(balCodeToString(mCode));
+ if (mProcessInfo != null) {
+ builder.append(" ");
+ builder.append(mProcessInfo);
+ }
+ return builder.toString();
+ }
+
+ public @BalCode int getCode() {
+ return mCode;
+ }
}
/**
* @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(
+ BalVerdict checkBackgroundActivityStart(
int callingUid,
int callingPid,
final String callingPackage,
@@ -221,36 +389,148 @@
BackgroundStartPrivileges backgroundStartPrivileges,
Intent intent,
ActivityOptions checkedOptions) {
+
+ 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)) {
+ BalVerdict balVerdict = new BalVerdict(BAL_ALLOW_SDK_SANDBOX, /*background*/ false,
+ "uid in SDK sandbox has visible (non-toast) window");
+ return statsLog(balVerdict, state);
+ }
+ }
+
+ BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
+ BalVerdict resultForRealCaller = callingUid == realCallingUid
+ ? resultForCaller // no need to calculate again
+ : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+
+ if (resultForCaller.allows()
+ && 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 statsLog(resultForCaller, state);
+ }
+ if (resultForRealCaller.allows()
+ && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start explicitly allowed by PI sender. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ return statsLog(resultForRealCaller, state);
+ }
+ if (resultForCaller.allows() && resultForRealCaller.allows()
+ && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+ && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ // Both caller and real caller allow with system defined behavior
+ 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 the realCaller result for backwards compatibility
+ return statsLog(resultForRealCaller, state);
+ }
+ if (resultForCaller.allows()
+ && 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 statsLog(resultForCaller, state);
+ }
+ if (resultForRealCaller.allows()
+ && 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 statsLog(resultForRealCaller, state);
+ }
+ 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 statsLog(BalVerdict.BLOCK, state);
+ }
+
+ /**
+ * @return A code denoting which BAL rule allows an activity to be started,
+ * or {@link #BAL_BLOCK} if the launch should be blocked
+ */
+ BalVerdict 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);
- 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");
- }
+ if (callingUid == Process.ROOT_UID
+ || callingAppId == Process.SYSTEM_UID
+ || callingAppId == Process.NFC_UID) {
+ return new BalVerdict(
+ BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
+ "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");
- }
+ // Always allow home application to start activities.
+ if (isHomeApp(callingUid, callingPackage)) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ false,
+ "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, callingUid, realCallingUid, intent,
- "Active ime");
- }
+ // IME should always be allowed to start activity, like IME settings.
+ final WindowState imeWindow =
+ mService.mRootWindowContainer.getCurrentInputMethodWindow();
+ if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ false,
+ "Active ime");
}
// This is used to block background activity launch even if the app is still
@@ -269,331 +549,169 @@
appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
final boolean allowCallingUidStartActivity =
((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
- && callingUidHasAnyVisibleWindow)
+ && callingUidHasAnyVisibleWindow)
|| isCallingUidPersistentSystemProcess;
- if (useCallingUidState && allowCallingUidStartActivity) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, callingUid, realCallingUid, intent,
+ if (allowCallingUidStartActivity) {
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ /*background*/ false,
"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 new BalVerdict(BAL_ALLOW_PERMISSION,
+ /*background*/ true,
"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");
- }
}
+ // don't abort if the caller has the same uid as the recents component
+ if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, "Recents Component");
+ }
+ // don't abort if the callingUid is the device owner
+ if (mService.isDeviceOwner(callingUid)) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, "Device Owner");
+ }
+ // don't abort if the callingUid is a affiliated profile owner
+ if (mService.isAffiliatedProfileOwner(callingUid)) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, "Affiliated Profile Owner");
+ }
+ // don't abort if the callingUid has companion device
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, "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 new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
+ /*background*/ true, "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 new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ "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 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)");
- }
- }
- // 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]";
+ // up and alive.
+ // Don't abort if the callerApp or other processes of that uid are allowed in any way.
+ BalVerdict callerAppAllowsBal = checkProcessAllowsBal(callerApp, state);
+ if (callerAppAllowsBal.allows()) {
+ return callerAppAllowsBal;
}
- 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);
- }
- }
- // 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));
- }
- return BAL_BLOCK;
+ // If we are here, it means all exemptions based on the creator failed
+ return BalVerdict.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
+ */
+ BalVerdict checkBackgroundActivityStartAllowedBySender(
+ BalState state,
+ ActivityOptions checkedOptions) {
+ int realCallingUid = state.mRealCallingUid;
+ BackgroundStartPrivileges backgroundStartPrivileges = state.mBackgroundStartPrivileges;
+
+ 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);
+ return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ /*background*/ false,
+ "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) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
+ if (state.mRealCallingUidHasAnyVisibleWindow) {
+ return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ /*background*/ false,
"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
+ if (state.mIsRealCallingUidPersistentSystemProcess
&& backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
+ return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ /*background*/ false,
"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,
+ return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+ /*background*/ false,
"realCallingUid is a companion app. "
- + "realCallingUid: " + realCallingUid, verdictLog);
+ + "realCallingUid: " + realCallingUid);
}
- return BAL_BLOCK;
+
+ // don't abort if the callerApp or other processes of that uid are allowed in any way
+ BalVerdict realCallerAppAllowsBal =
+ checkProcessAllowsBal(state.mRealCallerApp, state);
+ if (realCallerAppAllowsBal.allows()) {
+ return realCallerAppAllowsBal;
+ }
+
+ // If we are here, it means all exemptions based on PI sender failed
+ return BalVerdict.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 BalVerdict checkProcessAllowsBal(WindowProcessController app,
+ BalState state) {
+ if (app == null) {
+ return BalVerdict.BLOCK;
+ }
+ // first check the original calling process
+ final BalVerdict balAllowedForCaller = app
+ .areBackgroundActivityStartsAllowed(state.mAppSwitchState);
+ if (balAllowedForCaller.allows()) {
+ return balAllowedForCaller.withProcessInfo("callerApp process", app);
+ } 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);
+ if (proc != app) {
+ BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
+ state.mAppSwitchState);
+ if (balAllowedForUid.allows()) {
+ return balAllowedForCaller.withProcessInfo("process", proc);
+ }
+ }
+ }
+ }
+ }
+ return BalVerdict.BLOCK;
}
/**
@@ -1091,55 +1209,6 @@
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, String msg) {
- return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
- msg, VERDICT_ALLOWED);
- }
-
- /**
- * 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,
- 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("BAL Code: ");
- builder.append(balCodeToString(code));
- if (verdict.equals(VERDICT_ALLOWED)) {
- Slog.i(TAG, builder.toString());
- } else {
- Slog.d(TAG, builder.toString());
- }
- }
- return code;
- }
-
private static boolean isSystemExemptFlagEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_WINDOW_MANAGER,
@@ -1147,8 +1216,12 @@
/* defaultValue= */ true);
}
- private static void statsLogBalAllowed(
- @BalCode int code, int callingUid, int realCallingUid, Intent intent) {
+ private static BalVerdict statsLog(BalVerdict finalVerdict, BalState state) {
+ @BalCode int code = finalVerdict.getCode();
+ int callingUid = state.mCallingUid;
+ int realCallingUid = state.mRealCallingUid;
+ Intent intent = state.mIntent;
+
if (code == BAL_ALLOW_PENDING_INTENT
&& (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
String activityName =
@@ -1168,6 +1241,7 @@
callingUid,
realCallingUid);
}
+ return finalVerdict;
}
/**
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 527edc1..e849589 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -27,7 +27,6 @@
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
import static java.util.Objects.requireNonNull;
@@ -49,6 +48,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -96,37 +96,33 @@
mBackgroundActivityStartCallback = callback;
}
- @BackgroundActivityStartController.BalCode
- int areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
+ BalVerdict areBackgroundActivityStartsAllowed(
+ int pid, int uid, String packageName,
int appSwitchState, boolean isCheckingForFgsStart,
boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
long lastStopAppSwitchesTime, long lastActivityLaunchTime,
long lastActivityFinishTime) {
// Allow if the proc is instrumenting with background activity starts privs.
if (hasBackgroundActivityStartPrivileges) {
- return BackgroundActivityStartController.logStartAllowedAndReturnCode(
- BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null,
- pid, "Activity start allowed: process instrumenting with background "
- + "activity starts privileges");
+ return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ "Activity start allowed: process instrumenting with background "
+ + "activity starts privileges");
}
// Allow if the flag was explicitly set.
if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
- return BackgroundActivityStartController.logStartAllowedAndReturnCode(
- BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null,
- pid, "Activity start allowed: process allowed by token");
+ return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ "Activity start allowed: process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
if (isBoundByForegroundUid()) {
- return BackgroundActivityStartController.logStartAllowedAndReturnCode(
- BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, uid, uid, /*intent*/ null,
- pid, "Activity start allowed: process bound by foreground uid");
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+ "Activity start allowed: process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
if (hasActivityInVisibleTask
&& (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
- return BackgroundActivityStartController.logStartAllowedAndReturnCode(
- BAL_ALLOW_FOREGROUND, /*background*/ false, uid, uid, /*intent*/ null,
- pid, "Activity start allowed: process has activity in foreground task");
+ return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
+ "Activity start allowed: process has activity in foreground task");
}
// If app switching is not allowed, we ignore all the start activity grace period
@@ -141,9 +137,8 @@
// let app to be able to start background activity even it's in grace period.
if (lastActivityLaunchTime > lastStopAppSwitchesTime
|| lastActivityFinishTime > lastStopAppSwitchesTime) {
- return BackgroundActivityStartController.logStartAllowedAndReturnCode(
- BAL_ALLOW_GRACE_PERIOD, /*background*/ true, uid, uid, /*intent*/ null,
- pid, "Activity start allowed: within "
+ return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
+ "Activity start allowed: within "
+ ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
}
if (DEBUG_ACTIVITY_STARTS) {
@@ -154,7 +149,7 @@
}
}
- return BAL_BLOCK;
+ return BalVerdict.BLOCK;
}
/**
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
index d604402..5db02df 100644
--- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -58,7 +58,7 @@
* @param id The id of task that has been removed.
* @param userId The id of the user the task belonged to.
*/
- void removeSnap(int id, int userId) {
+ void removeSnapshot(int id, int userId) {
synchronized (mLock) {
mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
.createDeleteWriteQueueItem(id, userId, mPersistInfoProvider));
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 823fbc9..2fabb0e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -29,7 +29,7 @@
*/
public abstract class Dimmer {
- static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor();
+ static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
/**
* The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b7b5c2af..f314900 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -937,6 +937,7 @@
// so we still request the window to resize if the current frame is empty.
if (!w.getFrame().isEmpty()) {
w.updateLastFrames();
+ mWmService.mFrameChangingWindows.remove(w);
}
w.onResizeHandled();
}
@@ -1621,7 +1622,17 @@
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;
}
@@ -4705,6 +4716,17 @@
scheduleAnimation();
mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+ } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
+ // Even if the IME surface parent is not changed, the layer target belonging to the
+ // parent may have changes. Then attempt to reassign if the IME control target is
+ // possible to be the relative layer.
+ final SurfaceControl lastRelativeLayer = mImeWindowsContainer.getLastRelativeLayer();
+ if (lastRelativeLayer != mImeLayeringTarget.mSurfaceControl) {
+ assignRelativeLayerForIme(getSyncTransaction(), false /* forceUpdate */);
+ if (lastRelativeLayer != mImeWindowsContainer.getLastRelativeLayer()) {
+ scheduleAnimation();
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 17bfeb4..d69c5ef 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 @@
}
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 1a319ad..fa2c94a 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 @@
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 @@
// 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 @@
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/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f9fa9e6..f6aad4c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,7 +36,10 @@
import com.android.server.UiThread;
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
@@ -50,12 +53,12 @@
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
- private final Supplier<Boolean> mAreCornersRounded;
+ private final BooleanSupplier mAreCornersRounded;
private final Supplier<Color> mColorSupplier;
// Parameters for "blurred wallpaper" letterbox background.
- private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
- private final Supplier<Integer> mBlurRadiusSupplier;
- private final Supplier<Float> mDarkScrimAlphaSupplier;
+ private final BooleanSupplier mHasWallpaperBackgroundSupplier;
+ private final IntSupplier mBlurRadiusSupplier;
+ private final DoubleSupplier mDarkScrimAlphaSupplier;
private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
@@ -82,11 +85,11 @@
*/
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Boolean> areCornersRounded,
+ BooleanSupplier areCornersRounded,
Supplier<Color> colorSupplier,
- Supplier<Boolean> hasWallpaperBackgroundSupplier,
- Supplier<Integer> blurRadiusSupplier,
- Supplier<Float> darkScrimAlphaSupplier,
+ BooleanSupplier hasWallpaperBackgroundSupplier,
+ IntSupplier blurRadiusSupplier,
+ DoubleSupplier darkScrimAlphaSupplier,
IntConsumer doubleTapCallbackX,
IntConsumer doubleTapCallbackY,
Supplier<SurfaceControl> parentSurface) {
@@ -247,7 +250,7 @@
* Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
*/
private boolean useFullWindowSurface() {
- return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
+ return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean();
}
private final class TapEventReceiver extends InputEventReceiver {
@@ -424,7 +427,7 @@
mSurfaceFrameRelative.height());
t.reparent(mSurface, mParentSurface);
- mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
+ mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean();
updateAlphaAndBlur(t);
t.show(mSurface);
@@ -445,17 +448,17 @@
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- final float alpha = mDarkScrimAlphaSupplier.get();
+ final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble();
t.setAlpha(mSurface, alpha);
// Translucent dark scrim can be shown without blur.
- if (mBlurRadiusSupplier.get() <= 0) {
+ if (mBlurRadiusSupplier.getAsInt() <= 0) {
// Removing pre-exesting blur
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.get());
+ t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt());
}
private float[] getRgbColorArray() {
@@ -472,7 +475,7 @@
// and mParentSurface may never be updated in applySurfaceChanges but this
// doesn't mean that update is needed.
|| !mSurfaceFrameRelative.isEmpty()
- && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
+ && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground
|| !mColorSupplier.get().equals(mColor)
|| mParentSurfaceSupplier.get() != mParentSurface);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a442e7..c81105a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,7 @@
}
handleResizingWindows();
+ clearFrameChangingWindows();
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -1015,6 +1016,17 @@
}
/**
+ * Clears frame changing windows after handling moving and resizing windows.
+ */
+ private void clearFrameChangingWindows() {
+ final ArrayList<WindowState> frameChangingWindows = mWmService.mFrameChangingWindows;
+ for (int i = frameChangingWindows.size() - 1; i >= 0; i--) {
+ frameChangingWindows.get(i).updateLastFrames();
+ }
+ frameChangingWindows.clear();
+ }
+
+ /**
* @param w WindowState this method is applied to.
* @param obscured True if there is a window on top of this obscuring the display.
* @param syswin System window?
@@ -2039,6 +2051,8 @@
}
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
@@ -2223,6 +2237,7 @@
}
} finally {
transitionController.continueTransitionReady();
+ pipChangesApplied.meet();
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index f68e392..2be2a1a 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -26,6 +26,7 @@
import android.os.Trace;
import android.view.WindowManager;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -126,6 +127,18 @@
}
mActivitySnapshotController.handleTransitionFinish(windows);
mActivitySnapshotController.endSnapshotProcess();
+ // Remove task snapshot if it is visible at the end of transition.
+ for (int i = changeInfos.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = changeInfos.get(i).mContainer;
+ final Task task = wc.asTask();
+ if (task != null && wc.isVisibleRequested()) {
+ final TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(task.mTaskId,
+ task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */);
+ if (snapshot != null) {
+ mTaskSnapshotController.removeAndDeleteSnapshot(task.mTaskId, task.mUserId);
+ }
+ }
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4922e90..6cad16c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,7 +199,6 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
-import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -241,7 +240,6 @@
private static final String ATTR_ROOT_AFFINITY = "root_affinity";
private static final String ATTR_ROOTHASRESET = "root_has_reset";
private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
- private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete";
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
@@ -344,7 +342,6 @@
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
boolean autoRemoveRecents; // If true, we should automatically remove the task from
// recents when activity finishes
- boolean askedCompatMode;// Have asked the user about compat mode for this task.
private boolean mHasBeenVisible; // Set if any activities in the task have been visible
String stringName; // caching of toString() result.
@@ -489,8 +486,6 @@
private boolean mForceShowForAllUsers;
- private boolean mForceTranslucent = false;
-
// The display category name for this task.
String mRequiredDisplayCategory;
@@ -617,7 +612,7 @@
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
- boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+ boolean _autoRemoveRecents, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
@@ -652,7 +647,6 @@
rootWasReset = _rootWasReset;
isAvailable = true;
autoRemoveRecents = _autoRemoveRecents;
- askedCompatMode = _askedCompatMode;
mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
touchActiveTime();
@@ -1333,7 +1327,7 @@
clearRootProcess();
- mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+ mAtmService.mWindowManager.mTaskSnapshotController.removeAndDeleteSnapshot(
mTaskId, mUserId);
}
@@ -3304,7 +3298,7 @@
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
|| isTranslucent(null)) {
return super.getDimmer();
}
@@ -3647,7 +3641,7 @@
*/
TaskFragmentParentInfo getTaskFragmentParentInfo() {
return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
- shouldBeVisible(null /* starting */));
+ shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity());
}
@Override
@@ -3768,8 +3762,8 @@
pw.println(")");
}
pw.print(prefix); pw.print("Activities="); pw.println(mChildren);
- if (!askedCompatMode || !inRecents || !isAvailable) {
- pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode);
+ if (!inRecents || !isAvailable) {
+ pw.print(prefix);
pw.print(" inRecents="); pw.print(inRecents);
pw.print(" isAvailable="); pw.println(isAvailable);
}
@@ -3877,7 +3871,6 @@
}
out.attributeBoolean(null, ATTR_ROOTHASRESET, rootWasReset);
out.attributeBoolean(null, ATTR_AUTOREMOVERECENTS, autoRemoveRecents);
- out.attributeBoolean(null, ATTR_ASKEDCOMPATMODE, askedCompatMode);
out.attributeInt(null, ATTR_USERID, mUserId);
out.attributeBoolean(null, ATTR_USER_SETUP_COMPLETE, mUserSetupComplete);
out.attributeInt(null, ATTR_EFFECTIVE_UID, effectiveUid);
@@ -3975,7 +3968,6 @@
String windowLayoutAffinity = null;
boolean rootHasReset = false;
boolean autoRemoveRecents = false;
- boolean askedCompatMode = false;
int taskType = 0;
int userId = 0;
boolean userSetupComplete = true;
@@ -4036,9 +4028,6 @@
case ATTR_AUTOREMOVERECENTS:
autoRemoveRecents = Boolean.parseBoolean(attrValue);
break;
- case ATTR_ASKEDCOMPATMODE:
- askedCompatMode = Boolean.parseBoolean(attrValue);
- break;
case ATTR_USERID:
userId = Integer.parseInt(attrValue);
break;
@@ -4192,7 +4181,6 @@
.setOrigActivity(origActivity)
.setRootWasReset(rootHasReset)
.setAutoRemoveRecents(autoRemoveRecents)
- .setAskedCompatMode(askedCompatMode)
.setUserId(userId)
.setEffectiveUid(effectiveUid)
.setLastDescription(lastDescription)
@@ -4512,10 +4500,6 @@
return true;
}
- void setForceTranslucent(boolean set) {
- mForceTranslucent = set;
- }
-
@Override
public boolean isAlwaysOnTop() {
return !isForceHidden() && super.isAlwaysOnTop();
@@ -4533,11 +4517,6 @@
}
@Override
- protected boolean isForceTranslucent() {
- return mForceTranslucent;
- }
-
- @Override
long getProtoFieldId() {
return TASK;
}
@@ -4725,7 +4704,7 @@
}
if (isAttached()) {
setWindowingMode(WINDOWING_MODE_UNDEFINED);
- moveTaskToBackInner(this);
+ moveTaskToBackInner(this, null /* transition */);
}
if (top.isAttached()) {
top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
@@ -5728,24 +5707,27 @@
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);
@@ -5762,6 +5744,9 @@
if (mTransitionController.isShellTransitionsEnabled()) {
mAtmService.continueWindowLayout();
}
+ if (transition != null) {
+ movedToBack.meet();
+ }
}
ActivityRecord topActivity = getDisplayArea().topRunningActivity();
Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
@@ -6066,6 +6051,11 @@
// Non-root task position changed.
mRootWindowContainer.invalidateTaskLayers();
}
+
+ if (child.asActivityRecord() != null) {
+ // Send for TaskFragmentParentInfo#hasDirectActivity change.
+ sendTaskFragmentParentInfoChangedIfNeeded();
+ }
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
@@ -6269,7 +6259,6 @@
private ComponentName mOrigActivity;
private boolean mRootWasReset;
private boolean mAutoRemoveRecents;
- private boolean mAskedCompatMode;
private int mUserId;
private int mEffectiveUid;
private String mLastDescription;
@@ -6524,11 +6513,6 @@
return this;
}
- private Builder setAskedCompatMode(boolean askedCompatMode) {
- mAskedCompatMode = askedCompatMode;
- return this;
- }
-
private Builder setAffinityIntent(Intent affinityIntent) {
mAffinityIntent = affinityIntent;
return this;
@@ -6661,7 +6645,7 @@
Task buildInner() {
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
- mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
+ mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 906b3b5..00f2b89 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,7 +103,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -210,7 +209,7 @@
*/
int mMinHeight;
- Dimmer mDimmer = Flags.dimmerRefactor()
+ Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
/** Apply the dim layer on the embedded TaskFragment. */
@@ -375,6 +374,8 @@
@interface FlagForceHidden {}
protected int mForceHiddenFlags = 0;
+ private boolean mForceTranslucent = false;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -391,41 +392,6 @@
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
- private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
- new EnsureVisibleActivitiesConfigHelper();
- private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
- private boolean mUpdateConfig;
- private boolean mPreserveWindow;
- private boolean mBehindFullscreen;
-
- void reset(boolean preserveWindow) {
- mPreserveWindow = preserveWindow;
- mUpdateConfig = false;
- mBehindFullscreen = false;
- }
-
- void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.isVisibleRequested()) {
- return;
- }
- reset(preserveWindow);
- forAllActivities(this, start, true /* includeBoundary */,
- true /* traverseTopToBottom */);
-
- if (mUpdateConfig) {
- // Ensure the resumed state of the focus activity if we updated the configuration of
- // any activity.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
- mBehindFullscreen |= r.occludesParent();
- return mBehindFullscreen;
- }
- }
/** Creates an embedded task fragment. */
TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
@@ -879,8 +845,12 @@
return true;
}
- protected boolean isForceTranslucent() {
- return false;
+ boolean isForceTranslucent() {
+ return mForceTranslucent;
+ }
+
+ void setForceTranslucent(boolean set) {
+ mForceTranslucent = set;
}
boolean isLeafTaskFragment() {
@@ -1085,6 +1055,10 @@
return getActivity(ActivityRecord::canBeTopRunning);
}
+ /**
+ * Reports non-finishing activity count including this TaskFragment's child embedded
+ * TaskFragments' children activities.
+ */
int getNonFinishingActivityCount() {
final int[] runningActivityCount = new int[1];
forAllActivities(a -> {
@@ -1095,6 +1069,20 @@
return runningActivityCount[0];
}
+ /**
+ * Returns {@code true} if there's any non-finishing direct children activity, which is not
+ * embedded in TaskFragments
+ */
+ boolean hasNonFinishingDirectActivity() {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord activity = getChildAt(i).asActivityRecord();
+ if (activity != null && !activity.finishing) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
return r != null ? r.isFocusable()
@@ -2163,13 +2151,6 @@
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
- /**
- * Ensures all visible activities at or below the input activity have the right configuration.
- */
- void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
- mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
- }
-
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index ff766be..34ae370 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -157,6 +157,15 @@
*/
private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
+ /**
+ * Map from {@link TaskFragmentTransaction#getTransactionToken()} to a
+ * {@link Transition.ReadyCondition} that is waiting for the {@link TaskFragmentTransaction}
+ * to complete.
+ * @see #onTransactionHandled
+ */
+ private final ArrayMap<IBinder, Transition.ReadyCondition> mInFlightTransactions =
+ new ArrayMap<>();
+
TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
boolean isSystemOrganizer) {
mOrganizer = organizer;
@@ -173,7 +182,7 @@
@Override
public void binderDied() {
synchronized (mGlobalLock) {
- removeOrganizer(mOrganizer);
+ removeOrganizer(mOrganizer, "client died");
}
}
@@ -195,7 +204,7 @@
mOrganizedTaskFragments.remove(taskFragment);
}
- void dispose() {
+ void dispose(@NonNull String reason) {
boolean wasVisible = false;
for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(i);
@@ -236,6 +245,10 @@
// Cleanup any running transaction to unblock the current transition.
onTransactionFinished(mDeferredTransitions.keyAt(i));
}
+ for (int i = mInFlightTransactions.size() - 1; i >= 0; i--) {
+ // Cleanup any in-flight transactions to unblock the transition.
+ mInFlightTransactions.valueAt(i).meetAlternate("disposed(" + reason + ")");
+ }
mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */);
}
@@ -398,11 +411,6 @@
Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
return;
}
- onTransactionStarted(transaction.getTransactionToken());
- }
-
- /** Called when the transaction is sent to the organizer. */
- void onTransactionStarted(@NonNull IBinder transactionToken) {
if (!mWindowOrganizerController.getTransitionController().isCollecting()) {
return;
}
@@ -410,9 +418,13 @@
.getCollectingTransitionId();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Defer transition id=%d for TaskFragmentTransaction=%s", transitionId,
- transactionToken);
- mDeferredTransitions.put(transactionToken, transitionId);
+ transaction.getTransactionToken());
+ mDeferredTransitions.put(transaction.getTransactionToken(), transitionId);
mWindowOrganizerController.getTransitionController().deferTransitionReady();
+ final Transition.ReadyCondition transactionApplied = new Transition.ReadyCondition(
+ "task-fragment transaction", transaction);
+ mWindowOrganizerController.getTransitionController().waitFor(transactionApplied);
+ mInFlightTransactions.put(transaction.getTransactionToken(), transactionApplied);
}
/** Called when the transaction is finished. */
@@ -496,7 +508,7 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Unregister task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
- removeOrganizer(organizer);
+ removeOrganizer(organizer, "unregistered");
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -564,6 +576,11 @@
: null;
if (state != null) {
state.onTransactionFinished(transactionToken);
+ final Transition.ReadyCondition condition =
+ state.mInFlightTransactions.remove(transactionToken);
+ if (condition != null) {
+ condition.meet();
+ }
}
}
}
@@ -777,7 +794,8 @@
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
- private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull String reason) {
final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
organizer.asBinder());
if (state == null) {
@@ -788,7 +806,7 @@
// event dispatch as result of surface placement.
mPendingTaskFragmentEvents.remove(organizer.asBinder());
// remove all of the children of the organized TaskFragment
- state.dispose();
+ state.dispose(reason);
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 2b12e74..d8e18e4 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -284,9 +284,9 @@
}
}
- void notifyTaskRemovedFromRecents(int taskId, int userId) {
+ void removeAndDeleteSnapshot(int taskId, int userId) {
mCache.onIdRemoved(taskId);
- mPersister.onTaskRemovedFromRecents(taskId, userId);
+ mPersister.removeSnapshot(taskId, userId);
}
void removeSnapshotCache(int taskId) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3e8c017..233daad 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -67,10 +67,11 @@
* @param taskId The id of task that has been removed.
* @param userId The id of the user the task belonged to.
*/
- void onTaskRemovedFromRecents(int taskId, int userId) {
+ @Override
+ void removeSnapshot(int taskId, int userId) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
- super.removeSnap(taskId, userId);
+ super.removeSnapshot(taskId, userId);
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7d65c61..f6b4972 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 @@
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 @@
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 @@
*/
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 @@
/**
* 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 @@
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 @@
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 @@
// 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 @@
* {@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 @@
* 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 8ac21e4..28c24c8 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.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 @@
TransitionTracer mTransitionTracer;
private SystemPerformanceHinter mSystemPerformanceHinter;
+ private boolean mFullReadyTracking = false;
private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
new ArrayList<>();
@@ -281,6 +283,7 @@
registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
setSyncEngine(wms.mSyncEngine);
setSystemPerformanceHinter(wms.mSystemPerformanceHinter);
+ mFullReadyTracking = Flags.transitReadyTracking();
}
@VisibleForTesting
@@ -397,6 +400,14 @@
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 @@
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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4a0f44b..8f884d2f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2706,6 +2706,10 @@
return mLastLayer;
}
+ SurfaceControl getLastRelativeLayer() {
+ return mLastRelativeToLayer;
+ }
+
protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
if (mSurfaceFreezer.hasLeash()) {
// When the freezer has created animation leash parent for the window, set the layer
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index fbd226e..1456184 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -51,7 +51,8 @@
final Rect mFrame = new Rect();
/**
- * The last real frame that was reported to the client.
+ * The frame used to check if mFrame is changed, e.g., moved or resized. It will be committed
+ * after handling the moving or resizing windows.
*/
final Rect mLastFrame = new Rect();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 88f72f9..4a074ff 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -588,6 +588,12 @@
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
/**
+ * Windows that their frames are being changed. Used so we can clear the frame-changing states
+ * after handling the moved or resized windows.
+ */
+ final ArrayList<WindowState> mFrameChangingWindows = new ArrayList<>();
+
+ /**
* Mapping of displayId to {@link DisplayImePolicy}.
* Note that this can be accessed without holding the lock.
*/
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5ed6caf..a8b9417 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -37,6 +37,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
@@ -305,9 +306,12 @@
// 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 +319,7 @@
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 +334,15 @@
});
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 +350,9 @@
+ " 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 +361,17 @@
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.
@@ -746,8 +769,7 @@
}
}
- if ((c.getChangeMask()
- & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) {
+ if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
tr.setForceTranslucent(c.getForceTranslucent());
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -855,6 +877,11 @@
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
+ if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ taskFragment.setForceTranslucent(c.getForceTranslucent());
+ effects = TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
effects |= applyChanges(taskFragment, c);
if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
@@ -1159,9 +1186,13 @@
}
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;
}
@@ -1996,9 +2027,11 @@
if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
// System organizer is allowed to update the hidden and focusable state.
- // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+ // We unset the CHANGE_HIDDEN, CHANGE_FOCUSABLE, and CHANGE_FORCE_TRANSLUCENT bits
+ // because they are checked here.
changeMaskToBeChecked &= ~CHANGE_HIDDEN;
changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
+ changeMaskToBeChecked &= ~CHANGE_FORCE_TRANSLUCENT;
}
// setRelativeBounds is allowed.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index e769a27..a74a707 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -42,7 +42,6 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
import static com.android.server.wm.WindowManagerService.MY_PID;
import static java.util.Objects.requireNonNull;
@@ -631,22 +630,23 @@
*/
@HotPath(caller = HotPath.START_SERVICE)
public boolean areBackgroundFgsStartsAllowed() {
- return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(),
- true /* isCheckingForFgsStart */) != BAL_BLOCK;
+ return areBackgroundActivityStartsAllowed(
+ mAtm.getBalAppSwitchesState(),
+ true /* isCheckingForFgsStart */).allows();
}
- @BackgroundActivityStartController.BalCode
- int areBackgroundActivityStartsAllowed(int appSwitchState) {
- return areBackgroundActivityStartsAllowed(appSwitchState,
+ BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
+ int appSwitchState) {
+ return areBackgroundActivityStartsAllowed(
+ appSwitchState,
false /* isCheckingForFgsStart */);
}
- @BackgroundActivityStartController.BalCode
- private int areBackgroundActivityStartsAllowed(int appSwitchState,
- boolean isCheckingForFgsStart) {
- return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
- appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(),
- mInstrumentingWithBackgroundActivityStartPrivileges,
+ private BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
+ int appSwitchState, boolean isCheckingForFgsStart) {
+ return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid,
+ mInfo.packageName, appSwitchState, isCheckingForFgsStart,
+ hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges,
mAtm.getLastStopAppSwitchesTime(),
mLastActivityLaunchTime, mLastActivityFinishTime);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3a793e9..f14a6f9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1200,6 +1200,7 @@
void updateTrustedOverlay() {
mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
isWindowTrustedOverlay());
+ mInputWindowHandle.forceChange();
}
boolean isWindowTrustedOverlay() {
@@ -1346,6 +1347,11 @@
windowFrames.setContentChanged(true);
}
+ if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)
+ || !windowFrames.mRelFrame.equals(windowFrames.mLastRelFrame)) {
+ mWmService.mFrameChangingWindows.add(this);
+ }
+
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
mMovedByResize = true;
@@ -3716,10 +3722,6 @@
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
- // We update mLastFrame always rather than in the conditional with the last inset
- // variables, because mFrameSizeChanged only tracks the width and height changing.
- updateLastFrames();
-
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
diff --git a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
index a6084ea..cac13eb 100644
--- a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
+++ b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp
@@ -34,9 +34,12 @@
static constexpr uint64_t NSEC_PER_MSEC = 1000000;
-static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> ×,
- ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
- jlongArray tempForUidStats);
+static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
+ jlongArray outArray);
+
+static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
+ ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
+ jlongArray outBrackets);
static bool initialized = false;
static jclass class_KernelCpuStatsCallback;
@@ -62,25 +65,43 @@
return OK;
}
+static jboolean nativeIsSupportedFeature(JNIEnv *env) {
+ if (!android::bpf::startTrackingUidTimes()) {
+ return false;
+ }
+ auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
+ return totalByScalingStep.has_value();
+}
+
static jlong nativeReadCpuStats(JNIEnv *env, [[maybe_unused]] jobject zis, jobject callback,
jintArray scalingStepToPowerBracketMap,
- jlong lastUpdateTimestampNanos, jlongArray tempForUidStats) {
+ jlong lastUpdateTimestampNanos, jlongArray cpuTimeByScalingStep,
+ jlongArray tempForUidStats) {
+ ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap);
+
if (!initialized) {
if (init(env) == EXCEPTION) {
return 0L;
}
}
+ auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
+ if (!totalByScalingStep) {
+ jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Unsupported kernel feature");
+ return EXCEPTION;
+ }
+
+ if (flatten(env, *totalByScalingStep, cpuTimeByScalingStep) == EXCEPTION) {
+ return 0L;
+ }
+
uint64_t newLastUpdate = lastUpdateTimestampNanos;
auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
if (!data.has_value()) return lastUpdateTimestampNanos;
- ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap);
-
for (auto &[uid, times] : *data) {
- int status =
- extractUidStats(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats);
- if (status == EXCEPTION) {
+ if (combineByBracket(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats) ==
+ EXCEPTION) {
return 0L;
}
env->CallVoidMethod(callback, method_KernelCpuStatsCallback_processUidStats, (jint)uid,
@@ -89,13 +110,34 @@
return newLastUpdate;
}
-static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> ×,
- ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
- jlongArray tempForUidStats) {
- ScopedLongArrayRW scopedTempForStats(env, tempForUidStats);
- uint64_t *arrayForStats = reinterpret_cast<uint64_t *>(scopedTempForStats.get());
- const uint8_t statsSize = scopedTempForStats.size();
- memset(arrayForStats, 0, statsSize * sizeof(uint64_t));
+static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
+ jlongArray outArray) {
+ ScopedLongArrayRW scopedOutArray(env, outArray);
+ const uint8_t scalingStepCount = scopedOutArray.size();
+ uint64_t *out = reinterpret_cast<uint64_t *>(scopedOutArray.get());
+ uint32_t scalingStep = 0;
+ for (const auto &subVec : times) {
+ for (uint32_t i = 0; i < subVec.size(); ++i) {
+ if (scalingStep >= scalingStepCount) {
+ jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
+ "Array is too short, size=%u, scalingStep=%u",
+ scalingStepCount, scalingStep);
+ return EXCEPTION;
+ }
+ out[scalingStep] = subVec[i] / NSEC_PER_MSEC;
+ scalingStep++;
+ }
+ }
+ return OK;
+}
+
+static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
+ ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
+ jlongArray outBrackets) {
+ ScopedLongArrayRW scopedOutBrackets(env, outBrackets);
+ uint64_t *brackets = reinterpret_cast<uint64_t *>(scopedOutBrackets.get());
+ const uint8_t statsSize = scopedOutBrackets.size();
+ memset(brackets, 0, statsSize * sizeof(uint64_t));
const uint8_t scalingStepCount = scopedScalingStepToPowerBracketMap.size();
uint32_t scalingStep = 0;
@@ -108,14 +150,14 @@
scalingStepCount, scalingStep);
return EXCEPTION;
}
- uint32_t bucket = scopedScalingStepToPowerBracketMap[scalingStep];
- if (bucket >= statsSize) {
+ uint32_t bracket = scopedScalingStepToPowerBracketMap[scalingStep];
+ if (bracket >= statsSize) {
jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
- "UidStats array is too short, length=%u, bucket[%u]=%u",
- statsSize, scalingStep, bucket);
+ "Bracket array is too short, length=%u, bracket[%u]=%u",
+ statsSize, scalingStep, bracket);
return EXCEPTION;
}
- arrayForStats[bucket] += subVec[i] / NSEC_PER_MSEC;
+ brackets[bracket] += subVec[i] / NSEC_PER_MSEC;
scalingStep++;
}
}
@@ -123,7 +165,8 @@
}
static const JNINativeMethod method_table[] = {
- {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J)J",
+ {"nativeIsSupportedFeature", "()Z", (void *)nativeIsSupportedFeature},
+ {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J[J)J",
(void *)nativeReadCpuStats},
};
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 41706f0..8dfa685 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -23,8 +23,6 @@
"services.core",
"app-compat-annotations",
"service-permission.stubs.system_server",
- ],
- static_libs: [
"device_policy_aconfig_flags_lib",
],
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 922f69c..323d387 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -24,6 +24,7 @@
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import android.Manifest;
@@ -40,7 +41,6 @@
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
-import android.app.admin.flags.FlagUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -159,7 +159,7 @@
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -282,7 +282,7 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -428,7 +428,7 @@
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -499,7 +499,7 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1781,7 +1781,7 @@
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8509155..5f2d87c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -219,6 +219,7 @@
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -330,7 +331,6 @@
import android.app.admin.UnsafeStateException;
import android.app.admin.UserRestrictionPolicyKey;
import android.app.admin.WifiSsidPolicy;
-import android.app.admin.flags.FlagUtils;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
@@ -3430,7 +3430,7 @@
}
revertTransferOwnershipIfNecessaryLocked();
- if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (!policyEngineMigrationV2Enabled()) {
updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
}
}
@@ -21571,7 +21571,7 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
- if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (!policyEngineMigrationV2Enabled()) {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
@@ -21581,7 +21581,7 @@
}
synchronized (getLockObject()) {
- if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (policyEngineMigrationV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
caller.getPackageName(),
@@ -21621,7 +21621,7 @@
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
final CallerIdentity caller = getCallerIdentity(packageName);
- if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (policyEngineMigrationV2Enabled()) {
Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.USB_DATA_SIGNALING,
caller.getUserId());
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 924e2f8..0d024d6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2083,17 +2083,14 @@
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/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
index ca57f51..8b5d7f0 100644
--- a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -31,6 +31,7 @@
import android.app.smartspace.SmartspaceConfig;
import android.app.smartspace.SmartspaceSessionId;
import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.flags.Flags;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -165,7 +166,8 @@
}
Context ctx = getContext();
if (!(ctx.checkCallingPermission(MANAGE_SMARTSPACE) == PERMISSION_GRANTED
- || ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED
+ || (Flags.accessSmartspace()
+ && ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED)
|| mServiceNameResolver.isTemporary(userId)
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 6ff7b26..b63a58a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -37,6 +37,7 @@
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -598,6 +599,28 @@
false /* orientationPortrait */);
}
+ @Test
+ public void switchesKeyboardLayout_withShortcut_onlyIfImeVisible() throws Exception {
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isFalse();
+
+ verifyInputViewStatusOnMainSync(
+ () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ true /* expected */,
+ true /* inputViewStarted */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isTrue();
+ }
+
/**
* This checks that when the system navigation bar is not created (e.g. emulator),
* then the IME caption bar is also not created.
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index aaad669..2810145 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1051,7 +1051,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1092,7 +1091,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1135,7 +1133,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
@@ -1174,7 +1171,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
@@ -1222,7 +1218,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
@@ -1270,7 +1265,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1319,7 +1313,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1365,7 +1358,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 32e2871..a77a958 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -429,7 +429,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -451,7 +451,7 @@
SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(displayMode2.width, displayMode2.height,
- displayMode2.refreshRate));
+ displayMode2.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
@@ -485,7 +485,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -947,7 +947,7 @@
// Set the user preferred display mode
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(
- displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+ displayMode3.width, displayMode3.height, displayMode3.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
displayDeviceInfo = mListener.addedDisplays.get(
@@ -992,6 +992,52 @@
}
@Test
+ public void testGetAndSetDisplayModesDisambiguatesByVsyncRate() throws Exception {
+ SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f, 120f);
+ SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 60f, 60f);
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode1)).isTrue();
+ assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), displayMode1))
+ .isTrue();
+
+ display.dynamicInfo.preferredBootDisplayMode = 1;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice changedDisplayDevice = mListener.changedDisplays.get(0);
+ changedDisplayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = changedDisplayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+ assertThat(
+ matches(changedDisplayDevice.getSystemPreferredDisplayModeLocked(), displayMode2))
+ .isTrue();
+ }
+
+ @Test
public void testHdrSdrRatio_notifiesOnChange() throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
setUpDisplay(display);
@@ -1230,7 +1276,7 @@
private void assertModeIsSupported(Display.Mode[] supportedModes,
SurfaceControl.DisplayMode mode) {
assertThat(Arrays.stream(supportedModes).anyMatch(
- x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+ x -> x.matches(mode.width, mode.height, mode.peakRefreshRate))).isTrue();
}
private void assertModeIsSupported(Display.Mode[] supportedModes,
@@ -1244,7 +1290,7 @@
+ Arrays.toString(supportedModes);
Truth.assertWithMessage(message)
.that(Arrays.stream(supportedModes)
- .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+ .anyMatch(x -> x.matches(mode.width, mode.height, mode.peakRefreshRate)
&& Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
.isTrue();
}
@@ -1332,16 +1378,28 @@
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
float refreshRate) {
- return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate);
}
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
- float refreshRate, int group) {
+ float refreshRate,
+ float vsyncRate) {
+ return createFakeDisplayMode(id, width, height, refreshRate, vsyncRate, /* group */ 0);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, int group) {
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate, group);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, float vsyncRate, int group) {
final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
mode.id = id;
mode.width = width;
mode.height = height;
- mode.refreshRate = refreshRate;
+ mode.peakRefreshRate = refreshRate;
+ mode.vsyncRate = vsyncRate;
mode.xDpi = 100;
mode.yDpi = 100;
mode.group = group;
@@ -1444,6 +1502,9 @@
private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
- && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+ && Float.floatToIntBits(a.getRefreshRate())
+ == Float.floatToIntBits(b.peakRefreshRate)
+ && Float.floatToIntBits(a.getVsyncRate())
+ == Float.floatToIntBits(b.vsyncRate);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c493f84..37fe8d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2617,6 +2617,31 @@
assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(app).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+ // Start binding to a service that isn't running yet.
+ ServiceRecord sr = makeServiceRecord(app);
+ sr.app = null;
+ bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+
+ // Since sr.app is null, this service cannot be in the same process as the
+ // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
+ app.mServices.updateHasAboveClientLocked();
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ assertTrue(app.mServices.hasAboveClient());
+ assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();
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 bded9b4..809a0e8 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 @@
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 @@
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 610ea90..e7f1d16e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
@@ -34,6 +35,7 @@
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;
@@ -44,6 +46,8 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -97,6 +101,8 @@
@Mock
private LauncherApps mLauncherApps;
@Mock
+ private ActivityManager mActivityManager;
+ @Mock
private PackageManager mPackageManager;
@Mock
private PackageInstallerService mInstallerService;
@@ -122,6 +128,8 @@
private PackageSetting mPackageSetting;
+ private PackageManagerService mPackageManagerService;
+
private PackageArchiver mArchiveManager;
@Before
@@ -130,7 +138,7 @@
rule.system().stageNominalSystemState();
when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
- PackageManagerService pm = spy(new PackageManagerService(rule.mocks().getInjector(),
+ mPackageManagerService = spy(new PackageManagerService(rule.mocks().getInjector(),
/* factoryTest= */false,
MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
/* isEngBuild= */ false,
@@ -154,17 +162,24 @@
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
- doReturn(mComputer).when(pm).snapshotComputer();
+
+ 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());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
mock(Resources.class));
+ doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
+ .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+ eq(mUserId));
- mArchiveManager = spy(new PackageArchiver(mContext, pm));
+ 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));
}
@@ -237,6 +252,21 @@
}
@Test
+ public void archiveApp_installerDoesntSupportUnarchival() {
+ doReturn(new ParceledListSlice<>(List.of()))
+ .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+ eq(mUserId));
+
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ "Installer does not support unarchival");
+ }
+
+ @Test
public void archiveApp_noMainActivities() {
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
List.of());
@@ -254,7 +284,7 @@
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/powerstatstests/res/xml/power_profile_test.xml b/services/tests/powerstatstests/res/xml/power_profile_test.xml
new file mode 100644
index 0000000..ecd8861
--- /dev/null
+++ b/services/tests/powerstatstests/res/xml/power_profile_test.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<device name="Android">
+ <!-- This is the battery capacity in mAh -->
+ <item name="battery.capacity">3000</item>
+
+ <!-- Power consumption when CPU is suspended -->
+ <item name="cpu.suspend">5</item>
+ <!-- Additional power consumption when CPU is in a kernel idle loop -->
+ <item name="cpu.idle">1.11</item>
+ <!-- Additional power consumption by CPU excluding cluster and core when running -->
+ <item name="cpu.active">2.55</item>
+
+ <!-- Additional power consumption of CPU policy0 itself when running on related cores -->
+ <item name="cpu.scaling_policy_power.policy0">2.11</item>
+ <!-- Additional power consumption of CPU policy4 itself when running on related cores -->
+ <item name="cpu.scaling_policy_power.policy4">2.22</item>
+
+ <!-- Additional power used by a CPU related to policy3 when running at different
+ speeds. -->
+ <array name="cpu.scaling_step_power.policy0">
+ <value>10</value> <!-- 300 MHz CPU speed -->
+ <value>20</value> <!-- 1000 MHz CPU speed -->
+ <value>30</value> <!-- 1900 MHz CPU speed -->
+ </array>
+ <!-- Additional power used by a CPU related to policy3 when running at different
+ speeds. -->
+ <array name="cpu.scaling_step_power.policy4">
+ <value>25</value> <!-- 300 MHz CPU speed -->
+ <value>35</value> <!-- 1000 MHz CPU speed -->
+ <value>50</value> <!-- 2500 MHz CPU speed -->
+ <value>60</value> <!-- 3000 MHz CPU speed -->
+ </array>
+
+ <!-- Power used by display unit in ambient display mode, including back lighting-->
+ <item name="ambient.on">0.5</item>
+ <!-- Additional power used when screen is turned on at minimum brightness -->
+ <item name="screen.on">100</item>
+ <!-- Additional power used when screen is at maximum brightness, compared to
+ screen at minimum brightness -->
+ <item name="screen.full">800</item>
+
+ <!-- Average power used by the camera flash module when on -->
+ <item name="camera.flashlight">500</item>
+ <!-- Average power use by the camera subsystem for a typical camera
+ application. Intended as a rough estimate for an application running a
+ preview and capturing approximately 10 full-resolution pictures per
+ minute. -->
+ <item name="camera.avg">600</item>
+
+ <!-- Additional power used by the audio hardware, probably due to DSP -->
+ <item name="audio">100.0</item>
+
+ <!-- Additional power used by the video hardware, probably due to DSP -->
+ <item name="video">150.0</item> <!-- ~50mA -->
+
+ <!-- Additional power used when GPS is acquiring a signal -->
+ <item name="gps.on">10</item>
+
+ <!-- Additional power used when cellular radio is transmitting/receiving -->
+ <item name="radio.active">60</item>
+ <!-- Additional power used when cellular radio is paging the tower -->
+ <item name="radio.scanning">3</item>
+ <!-- Additional power used when the cellular radio is on. Multi-value entry,
+ one per signal strength (no signal, weak, moderate, strong) -->
+ <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+ <value>6</value> <!-- none -->
+ <value>5</value> <!-- poor -->
+ <value>4</value> <!-- moderate -->
+ <value>3</value> <!-- good -->
+ <value>3</value> <!-- great -->
+ </array>
+
+ <!-- Cellular modem related values. These constants are deprecated, but still supported and
+ need to be tested -->
+ <item name="modem.controller.sleep">123</item>
+ <item name="modem.controller.idle">456</item>
+ <item name="modem.controller.rx">789</item>
+ <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+ <value>10</value>
+ <value>20</value>
+ <value>30</value>
+ <value>40</value>
+ <value>50</value>
+ </array>
+</device>
\ No newline at end of file
diff --git a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml b/services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml
similarity index 100%
rename from core/tests/coretests/res/xml/power_profile_test_power_brackets.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
new file mode 100644
index 0000000..48e2dd7
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatePowerStatsProcessorTest {
+
+ @Test
+ public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() {
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
+
+ AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
+ new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+ assertThat(deviceStateEstimatesToStrings(plan))
+ .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
+ assertThat(combinedDeviceStatsToStrings(plan))
+ .containsExactly("[[0, 0]]", "[[0, 1]]", "[[1, 0]]", "[[1, 1]]");
+ assertThat(uidStateEstimatesToStrings(plan, config))
+ .containsExactly(
+ "[[0, 0]]: [ps]: [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4]]",
+ "[[0, 1]]: [ps]: [[0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4]]",
+ "[[1, 0]]: [ps]: [[1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 0, 3], [1, 0, 4]]",
+ "[[1, 1]]: [ps]: [[1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]]");
+ }
+
+ @Test
+ public void createPowerEstimationPlan_combineDeviceStats() {
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_PROCESS_STATE);
+
+ AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
+ new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+
+ assertThat(deviceStateEstimatesToStrings(plan))
+ .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
+ assertThat(combinedDeviceStatsToStrings(plan))
+ .containsExactly(
+ "[[0, 0], [0, 1]]",
+ "[[1, 0], [1, 1]]");
+ assertThat(uidStateEstimatesToStrings(plan, config))
+ .containsExactly(
+ "[[0, 0], [0, 1]]: [ps]: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]]",
+ "[[1, 0], [1, 1]]: [ps]: [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]]");
+ }
+
+ private static List<String> deviceStateEstimatesToStrings(
+ AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+ return plan.deviceStateEstimations.stream()
+ .map(dse -> dse.stateValues).map(Arrays::toString).toList();
+ }
+
+ private static List<String> combinedDeviceStatsToStrings(
+ AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+ return plan.combinedDeviceStateEstimations.stream()
+ .map(cds -> cds.deviceStateEstimations)
+ .map(dses -> dses.stream()
+ .map(dse -> dse.stateValues).map(Arrays::toString).toList())
+ .map(Object::toString)
+ .toList();
+ }
+
+ private static List<String> uidStateEstimatesToStrings(
+ AggregatedPowerStatsProcessor.PowerEstimationPlan plan,
+ AggregatedPowerStatsConfig.PowerComponent config) {
+ MultiStateStats.States[] uidStateConfig = config.getUidStateConfig();
+ return plan.uidStateEstimates.stream()
+ .map(use ->
+ use.combinedDeviceStateEstimate.deviceStateEstimations.stream()
+ .map(dse -> dse.stateValues).map(Arrays::toString).toList()
+ + ": "
+ + Arrays.stream(use.states)
+ .filter(Objects::nonNull)
+ .map(MultiStateStats.States::getName).toList()
+ + ": "
+ + use.proportionalEstimates.stream()
+ .map(pe -> trackedStatesToString(uidStateConfig, pe.stateValues))
+ .toList())
+ .toList();
+ }
+
+ private static Object trackedStatesToString(MultiStateStats.States[] states,
+ int[] stateValues) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ boolean first = true;
+ for (int i = 0; i < states.length; i++) {
+ if (!states[i].isTracked()) {
+ continue;
+ }
+
+ if (!first) {
+ sb.append(", ");
+ }
+ first = false;
+ sb.append(stateValues[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 3579fce..0b10954 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -142,6 +142,22 @@
return this;
}
+ /**
+ * Mocks the CPU bracket count
+ */
+ public BatteryUsageStatsRule setCpuPowerBracketCount(int count) {
+ when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(count);
+ return this;
+ }
+
+ /**
+ * Mocks the CPU bracket for the given CPU scaling policy and step
+ */
+ public BatteryUsageStatsRule setCpuPowerBracket(int policy, int step, int bracket) {
+ when(mPowerProfile.getCpuPowerBracketForScalingStep(policy, step)).thenReturn(bracket);
+ return this;
+ }
+
public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal,
double value) {
when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
new file mode 100644
index 0000000..79084cc
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.LongArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CpuAggregatedPowerStatsProcessorTest {
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+ .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
+ .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300})
+ .setAveragePowerForCpuScalingPolicy(0, 360)
+ .setAveragePowerForCpuScalingPolicy(2, 480)
+ .setAveragePowerForCpuScalingStep(0, 0, 300)
+ .setAveragePowerForCpuScalingStep(0, 1, 400)
+ .setAveragePowerForCpuScalingStep(2, 0, 500)
+ .setCpuPowerBracketCount(3)
+ .setCpuPowerBracket(0, 0, 0)
+ .setCpuPowerBracket(0, 1, 1)
+ .setCpuPowerBracket(2, 0, 2);
+
+ private AggregatedPowerStatsConfig.PowerComponent mConfig;
+ private CpuAggregatedPowerStatsProcessor mProcessor;
+ private MockPowerComponentAggregatedPowerStats mStats;
+
+ @Before
+ public void setup() {
+ mConfig = new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
+
+ mProcessor = new CpuAggregatedPowerStatsProcessor(
+ mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies());
+ }
+
+ @Test
+ public void powerProfileModel() {
+ mStats = new MockPowerComponentAggregatedPowerStats(mConfig, false);
+ mStats.setDeviceStats(
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON),
+ concat(
+ values(3500, 4500, 3000), // scaling steps
+ values(2000, 1000), // clusters
+ values(5000)), // uptime
+ 3.113732);
+ mStats.setDeviceStats(
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON),
+ concat(
+ values(6000, 6500, 4000),
+ values(5000, 3000),
+ values(7000)),
+ 4.607245);
+ mStats.setDeviceStats(
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER),
+ concat(
+ values(9000, 10000, 7000),
+ values(8000, 6000),
+ values(20000)),
+ 7.331799);
+ mStats.setUidStats(24,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+ values(400, 1500, 2000), 1.206947);
+ mStats.setUidStats(42,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+ values(900, 1000, 1500), 1.016182);
+ mStats.setUidStats(42,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
+ values(600, 500, 300), 0.385042);
+ mStats.setUidStats(42,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
+ values(1500, 2000, 1000), 1.252578);
+
+ mProcessor.finish(mStats);
+
+ mStats.verifyPowerEstimates();
+ }
+
+ @Test
+ public void energyConsumerModel() {
+ mStats = new MockPowerComponentAggregatedPowerStats(mConfig, true);
+ mStats.setDeviceStats(
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON),
+ concat(
+ values(3500, 4500, 3000), // scaling steps
+ values(2000, 1000), // clusters
+ values(5000), // uptime
+ values(5_000_000L, 6_000_000L)), // energy, uC
+ 3.055555);
+ mStats.setDeviceStats(
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON),
+ concat(
+ values(6000, 6500, 4000),
+ values(5000, 3000),
+ values(7000),
+ values(5_000_000L, 6_000_000L)), // same as above
+ 3.055555); // same as above - WAI
+ mStats.setDeviceStats(
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER),
+ concat(
+ values(9000, 10000, 7000),
+ values(8000, 6000),
+ values(20000),
+ values(8_000_000L, 18_000_000L)),
+ 7.222222);
+ mStats.setUidStats(24,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+ values(400, 1500, 2000), 1.449078);
+ mStats.setUidStats(42,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND),
+ values(900, 1000, 1500), 1.161902);
+ mStats.setUidStats(42,
+ states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND),
+ values(600, 500, 300), 0.355406);
+ mStats.setUidStats(42,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED),
+ values(1500, 2000, 1000), 0.80773);
+
+ mProcessor.finish(mStats);
+
+ mStats.verifyPowerEstimates();
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private long[] values(long... values) {
+ return values;
+ }
+
+ private long[] concat(long[]... arrays) {
+ LongArray all = new LongArray();
+ for (long[] array : arrays) {
+ for (long value : array) {
+ all.add(value);
+ }
+ }
+ return all.toArray();
+ }
+
+ private static class MockPowerComponentAggregatedPowerStats extends
+ PowerComponentAggregatedPowerStats {
+ private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout;
+ private final PowerStats.Descriptor mDescriptor;
+ private HashMap<String, long[]> mDeviceStats = new HashMap<>();
+ private HashMap<String, long[]> mUidStats = new HashMap<>();
+ private HashSet<Integer> mUids = new HashSet<>();
+ private HashMap<String, Double> mExpectedDevicePower = new HashMap<>();
+ private HashMap<String, Double> mExpectedUidPower = new HashMap<>();
+
+ MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config,
+ boolean useEnergyConsumers) {
+ super(config);
+ mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout();
+ mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3);
+ mStatsLayout.addDeviceSectionCpuTimeByCluster(2);
+ mStatsLayout.addDeviceSectionUptime();
+ if (useEnergyConsumers) {
+ mStatsLayout.addDeviceSectionEnergyConsumers(2);
+ }
+ mStatsLayout.addDeviceSectionPowerEstimate();
+ mStatsLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0, 1, 2});
+ mStatsLayout.addUidSectionPowerEstimate();
+
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
+ mStatsLayout.getDeviceStatsArrayLength(), mStatsLayout.getUidStatsArrayLength(),
+ extras);
+ }
+
+ @Override
+ public PowerStats.Descriptor getPowerStatsDescriptor() {
+ return mDescriptor;
+ }
+
+ @Override
+ boolean getDeviceStats(long[] outValues, int[] deviceStates) {
+ long[] values = getDeviceStats(deviceStates);
+ System.arraycopy(values, 0, outValues, 0, values.length);
+ return true;
+ }
+
+ private long[] getDeviceStats(int[] deviceStates) {
+ String key = statesToString(getConfig().getDeviceStateConfig(), deviceStates);
+ long[] values = mDeviceStats.get(key);
+ return values == null ? new long[mDescriptor.statsArrayLength] : values;
+ }
+
+ void setDeviceStats(int[] states, long[] values, double expectedPowerEstimate) {
+ setDeviceStats(states, values);
+ mExpectedDevicePower.put(statesToString(getConfig().getDeviceStateConfig(), states),
+ expectedPowerEstimate);
+ }
+
+ @Override
+ void setDeviceStats(int[] states, long[] values) {
+ String key = statesToString(getConfig().getDeviceStateConfig(), states);
+ mDeviceStats.put(key, Arrays.copyOf(values, mDescriptor.statsArrayLength));
+ }
+
+ @Override
+ boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
+ long[] values = getUidStats(uid, uidStates);
+ assertThat(values).isNotNull();
+ System.arraycopy(values, 0, outValues, 0, values.length);
+ return true;
+ }
+
+ private long[] getUidStats(int uid, int[] uidStates) {
+ String key = uid + " " + statesToString(getConfig().getUidStateConfig(), uidStates);
+ long[] values = mUidStats.get(key);
+ return values == null ? new long[mDescriptor.uidStatsArrayLength] : values;
+ }
+
+ void setUidStats(int uid, int[] states, long[] values, double expectedPowerEstimate) {
+ setUidStats(uid, states, values);
+ mExpectedUidPower.put(
+ uid + " " + statesToString(getConfig().getUidStateConfig(), states),
+ expectedPowerEstimate);
+ }
+
+ @Override
+ void setUidStats(int uid, int[] states, long[] values) {
+ mUids.add(uid);
+ String key = uid + " " + statesToString(getConfig().getUidStateConfig(), states);
+ mUidStats.put(key, Arrays.copyOf(values, mDescriptor.uidStatsArrayLength));
+ }
+
+ @Override
+ void collectUids(Collection<Integer> uids) {
+ uids.addAll(mUids);
+ }
+
+ void verifyPowerEstimates() {
+ StringBuilder mismatches = new StringBuilder();
+ for (Map.Entry<String, Double> entry : mExpectedDevicePower.entrySet()) {
+ String key = entry.getKey();
+ double expected = mExpectedDevicePower.get(key);
+ double actual = mStatsLayout.getDevicePowerEstimate(mDeviceStats.get(key));
+ if (Math.abs(expected - actual) > 0.005) {
+ mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n");
+ }
+ }
+ for (Map.Entry<String, Double> entry : mExpectedUidPower.entrySet()) {
+ String key = entry.getKey();
+ double expected = mExpectedUidPower.get(key);
+ double actual = mStatsLayout.getUidPowerEstimate(mUidStats.get(key));
+ if (Math.abs(expected - actual) > 0.005) {
+ mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n");
+ }
+ }
+ if (!mismatches.isEmpty()) {
+ fail("Unexpected power estimations:\n" + mismatches);
+ }
+ }
+
+ private String statesToString(MultiStateStats.States[] config, int[] states) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < states.length; i++) {
+ sb.append(config[i].getName()).append("=").append(states[i]).append(" ");
+ }
+ return sb.toString();
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index f2ee6db..bc211df 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -20,16 +20,26 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.power.PowerStatsInternal;
import android.util.SparseArray;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.frameworks.powerstatstests.R;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
@@ -40,85 +50,266 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CpuPowerStatsCollectorTest {
+ private Context mContext;
private final MockClock mMockClock = new MockClock();
private final HandlerThread mHandlerThread = new HandlerThread("test");
private Handler mHandler;
- private CpuPowerStatsCollector mCollector;
private PowerStats mCollectedStats;
- @Mock
private PowerProfile mPowerProfile;
@Mock
private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
+ @Mock
+ private PowerStatsInternal mPowerStatsInternal;
+ private CpuScalingPolicies mCpuScalingPolicies;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getContext();
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
- when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2);
- when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0);
- when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1);
- mCollector = new CpuPowerStatsCollector(new CpuScalingPolicies(
- new SparseArray<>() {{
- put(0, new int[]{0});
- }},
- new SparseArray<>() {{
- put(0, new int[]{1, 12});
- }}),
- mPowerProfile, mHandler, mMockKernelCpuStatsReader, 60_000, mMockClock);
- mCollector.addConsumer(stats -> mCollectedStats = stats);
- mCollector.setEnabled(true);
+ when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true);
}
@Test
- public void collectStats() {
- mockKernelCpuStats(new SparseArray<>() {{
- put(42, new long[]{100, 200});
- put(99, new long[]{300, 600});
- }}, 0, 1234);
+ public void powerBrackets_specifiedInPowerProfile() {
+ mPowerProfile = new PowerProfile(mContext);
+ mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
+ mCpuScalingPolicies = new CpuScalingPolicies(
+ new SparseArray<>() {{
+ put(0, new int[]{0});
+ put(4, new int[]{4});
+ }},
+ new SparseArray<>() {{
+ put(0, new int[]{100});
+ put(4, new int[]{400, 500});
+ }});
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+
+ assertThat(getScalingStepToPowerBracketMap(collector))
+ .isEqualTo(new int[]{1, 1, 0});
+ }
+
+ @Test
+ public void powerBrackets_default_noEnergyConsumers() {
+ mPowerProfile = new PowerProfile(mContext);
+ mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mockCpuScalingPolicies(2);
+
+ CpuPowerStatsCollector collector = createCollector(3, 0);
+
+ assertThat(new String[]{
+ collector.getCpuPowerBracketDescription(0),
+ collector.getCpuPowerBracketDescription(1),
+ collector.getCpuPowerBracketDescription(2)})
+ .isEqualTo(new String[]{
+ "0/300(10.0)",
+ "0/1000(20.0), 0/2000(30.0), 4/300(25.0)",
+ "4/1000(35.0), 4/2500(50.0), 4/3000(60.0)"});
+ assertThat(getScalingStepToPowerBracketMap(collector))
+ .isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2});
+ }
+
+ @Test
+ public void powerBrackets_moreBracketsThanStates() {
+ mPowerProfile = new PowerProfile(mContext);
+ mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mockCpuScalingPolicies(2);
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+
+ assertThat(getScalingStepToPowerBracketMap(collector))
+ .isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6});
+ }
+
+ @Test
+ public void powerBrackets_energyConsumers() throws Exception {
+ mPowerProfile = new PowerProfile(mContext);
+ mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mockCpuScalingPolicies(2);
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 2);
+
+ assertThat(getScalingStepToPowerBracketMap(collector))
+ .isEqualTo(new int[]{0, 1, 1, 2, 2, 3, 3});
+ }
+
+ @Test
+ public void powerStatsDescriptor() throws Exception {
+ mPowerProfile = new PowerProfile(mContext);
+ mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mockCpuScalingPolicies(2);
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 2);
+ PowerStats.Descriptor descriptor = collector.getPowerStatsDescriptor();
+ assertThat(descriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
+ assertThat(descriptor.name).isEqualTo("cpu");
+ assertThat(descriptor.statsArrayLength).isEqualTo(13);
+ assertThat(descriptor.uidStatsArrayLength).isEqualTo(5);
+ CpuPowerStatsCollector.StatsArrayLayout layout =
+ new CpuPowerStatsCollector.StatsArrayLayout();
+ layout.fromExtras(descriptor.extras);
+
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ layout.setTimeByScalingStep(deviceStats, 2, 42);
+ layout.setConsumedEnergy(deviceStats, 1, 43);
+ layout.setUptime(deviceStats, 44);
+ layout.setDevicePowerEstimate(deviceStats, 45);
+
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ layout.setUidTimeByPowerBracket(uidStats, 3, 46);
+ layout.setUidPowerEstimate(uidStats, 47);
+
+ assertThat(layout.getCpuScalingStepCount()).isEqualTo(7);
+ assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42);
+
+ assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2);
+ assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43);
+
+ assertThat(layout.getUptime(deviceStats)).isEqualTo(44);
+
+ assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45);
+
+ assertThat(layout.getScalingStepToPowerBracketMap()).isEqualTo(
+ new int[]{0, 1, 1, 2, 2, 3, 3});
+ assertThat(layout.getCpuPowerBracketCount()).isEqualTo(4);
+
+ assertThat(layout.getUidTimeByPowerBracket(uidStats, 3)).isEqualTo(46);
+ assertThat(layout.getUidPowerEstimate(uidStats)).isEqualTo(47);
+ }
+
+ @Test
+ public void collectStats() throws Exception {
+ mockCpuScalingPolicies(1);
+ mockPowerProfile();
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+ CpuPowerStatsCollector.StatsArrayLayout layout =
+ new CpuPowerStatsCollector.StatsArrayLayout();
+ layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+ mockKernelCpuStats(new long[]{1111, 2222, 3333},
+ new SparseArray<>() {{
+ put(42, new long[]{100, 200});
+ put(99, new long[]{300, 600});
+ }}, 0, 1234);
mMockClock.uptime = 1000;
- mCollector.forceSchedule();
+ collector.forceSchedule();
waitForIdle();
assertThat(mCollectedStats.durationMs).isEqualTo(1234);
- assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{100, 200});
- assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{300, 600});
- mockKernelCpuStats(new SparseArray<>() {{
- put(42, new long[]{123, 234});
- put(99, new long[]{345, 678});
- }}, 1234, 3421);
+ assertThat(layout.getCpuScalingStepCount()).isEqualTo(3);
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(1111);
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222);
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(3333);
+
+ assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0);
+ assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0);
+
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+ .isEqualTo(100);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+ .isEqualTo(200);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+ .isEqualTo(300);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+ .isEqualTo(600);
+
+ mockKernelCpuStats(new long[]{5555, 4444, 3333},
+ new SparseArray<>() {{
+ put(42, new long[]{123, 234});
+ put(99, new long[]{345, 678});
+ }}, 1234, 3421);
mMockClock.uptime = 2000;
- mCollector.forceSchedule();
+ collector.forceSchedule();
waitForIdle();
assertThat(mCollectedStats.durationMs).isEqualTo(3421 - 1234);
- assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{23, 34});
- assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{45, 78});
+
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(4444);
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222);
+ assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(0);
+
+ // 500 * 1000 / 3500
+ assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(143);
+ // 700 * 1000 / 3500
+ assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200);
+
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0))
+ .isEqualTo(23);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1))
+ .isEqualTo(34);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0))
+ .isEqualTo(45);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1))
+ .isEqualTo(78);
}
- private void mockKernelCpuStats(SparseArray<long[]> uidToCpuStats,
+ private void mockCpuScalingPolicies(int clusterCount) {
+ SparseArray<int[]> cpus = new SparseArray<>();
+ SparseArray<int[]> freqs = new SparseArray<>();
+ cpus.put(0, new int[]{0, 1, 2, 3});
+ freqs.put(0, new int[]{300000, 1000000, 2000000});
+ if (clusterCount == 2) {
+ cpus.put(4, new int[]{4, 5});
+ freqs.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ }
+ mCpuScalingPolicies = new CpuScalingPolicies(cpus, freqs);
+ }
+
+ private void mockPowerProfile() {
+ mPowerProfile = mock(PowerProfile.class);
+ when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2);
+ when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0);
+ when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1);
+ when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 2)).thenReturn(1);
+ }
+
+ private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
+ int defaultCpuPowerBracketsPerEnergyConsumer) {
+ CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies,
+ mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal,
+ () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets,
+ defaultCpuPowerBracketsPerEnergyConsumer);
+ collector.addConsumer(stats -> mCollectedStats = stats);
+ collector.setEnabled(true);
+ return collector;
+ }
+
+ private void mockKernelCpuStats(long[] deviceStats, SparseArray<long[]> uidToCpuStats,
long expectedLastUpdateTimestampMs, long newLastUpdateTimestampMs) {
when(mMockKernelCpuStatsReader.nativeReadCpuStats(
any(CpuPowerStatsCollector.KernelCpuStatsCallback.class),
- any(int[].class), anyLong(), any(long[].class)))
+ any(int[].class), anyLong(), any(long[].class), any(long[].class)))
.thenAnswer(invocation -> {
CpuPowerStatsCollector.KernelCpuStatsCallback callback =
invocation.getArgument(0);
int[] powerBucketIndexes = invocation.getArgument(1);
long lastTimestamp = invocation.getArgument(2);
- long[] tempStats = invocation.getArgument(3);
+ long[] cpuTimeByScalingStep = invocation.getArgument(3);
+ long[] tempStats = invocation.getArgument(4);
- assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1});
+ assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1, 1});
assertThat(lastTimestamp / 1000000L).isEqualTo(expectedLastUpdateTimestampMs);
assertThat(tempStats).hasLength(2);
+ System.arraycopy(deviceStats, 0, cpuTimeByScalingStep, 0,
+ cpuTimeByScalingStep.length);
+
for (int i = 0; i < uidToCpuStats.size(); i++) {
int uid = uidToCpuStats.keyAt(i);
long[] cpuStats = uidToCpuStats.valueAt(i);
@@ -129,6 +320,67 @@
});
}
+ @SuppressWarnings("unchecked")
+ private void mockEnergyConsumers() throws Exception {
+ when(mPowerStatsInternal.getEnergyConsumerInfo())
+ .thenReturn(new EnergyConsumer[]{
+ new EnergyConsumer() {{
+ id = 1;
+ type = EnergyConsumerType.CPU_CLUSTER;
+ ordinal = 0;
+ name = "CPU0";
+ }},
+ new EnergyConsumer() {{
+ id = 2;
+ type = EnergyConsumerType.CPU_CLUSTER;
+ ordinal = 1;
+ name = "CPU4";
+ }},
+ new EnergyConsumer() {{
+ id = 3;
+ type = EnergyConsumerType.BLUETOOTH;
+ name = "BT";
+ }},
+ });
+
+ CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class);
+ when(future1.get(anyLong(), any(TimeUnit.class)))
+ .thenReturn(new EnergyConsumerResult[]{
+ new EnergyConsumerResult() {{
+ id = 1;
+ energyUWs = 1000;
+ }},
+ new EnergyConsumerResult() {{
+ id = 2;
+ energyUWs = 2000;
+ }}
+ });
+
+ CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class);
+ when(future2.get(anyLong(), any(TimeUnit.class)))
+ .thenReturn(new EnergyConsumerResult[]{
+ new EnergyConsumerResult() {{
+ id = 1;
+ energyUWs = 1500;
+ }},
+ new EnergyConsumerResult() {{
+ id = 2;
+ energyUWs = 2700;
+ }}
+ });
+
+ when(mPowerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2})))
+ .thenReturn(future1)
+ .thenReturn(future2);
+ }
+
+ private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) {
+ CpuPowerStatsCollector.StatsArrayLayout layout =
+ new CpuPowerStatsCollector.StatsArrayLayout();
+ layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+ return layout.getScalingStepToPowerBracketMap();
+ }
+
private void waitForIdle() {
ConditionVariable done = new ConditionVariable();
mHandler.post(done::open);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 30a73181..eb03a6c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -180,7 +180,7 @@
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
- multiStateStats.dump(pw);
+ multiStateStats.dump(pw, Arrays::toString);
assertThat(sw.toString()).isEqualTo(
"plugged-in fg [25, 50]\n"
+ "on-battery fg [25, 50]\n"
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 71007f5..52a5d8f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -156,6 +156,7 @@
mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+ mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -178,6 +179,7 @@
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
+ assertFalse(mUserState.isMagnificationTwoFingerTripleTapEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
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 3808f30..bfaf4959 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.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.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.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.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 @@
@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 @@
@Before
public void setup() throws RemoteException {
+ mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+
MockitoAnnotations.initMocks(this);
final Resources resources = InstrumentationRegistry.getContext().getResources();
@@ -121,6 +131,8 @@
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 @@
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 24a628e..d26d671 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 e3e708e..0230d77 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.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.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 @@
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/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 5cc84b1..78bf9b0 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -44,7 +43,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.pm.UserManagerInternal;
@@ -94,7 +93,7 @@
@Mock private UserManagerInternal mMockUserManagerInternal;
- @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+ @Mock private ContentProtectionAllowlistManager mMockContentProtectionAllowlistManager;
@Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
@@ -108,7 +107,7 @@
private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
- private int mContentProtectionBlocklistManagersCreated;
+ private int mContentProtectionAllowlistManagersCreated;
private int mContentProtectionServiceInfosCreated;
@@ -132,10 +131,10 @@
@Test
public void constructor_contentProtection_flagDisabled_noManagers() {
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -145,10 +144,10 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -158,10 +157,10 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -171,26 +170,13 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
- public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
- mDevCfgEnableContentProtectionReceiver = true;
- mContentCaptureManagerService = new TestContentCaptureManagerService();
- mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
-
- mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
-
- verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
- }
-
- @Test
public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -201,13 +187,13 @@
assertThat(actual).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -239,13 +225,13 @@
assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
assertThat(actual.whitelistedComponents).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -273,7 +259,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -287,13 +273,13 @@
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -317,7 +303,7 @@
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -331,7 +317,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -345,13 +331,13 @@
USER_ID, COMPONENT_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -375,13 +361,13 @@
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void isContentProtectionReceiverEnabled_true() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -400,7 +386,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -415,7 +401,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -431,7 +417,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -572,9 +558,9 @@
}
@Override
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- mContentProtectionBlocklistManagersCreated++;
- return mMockContentProtectionBlocklistManager;
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ mContentProtectionAllowlistManagersCreated++;
+ return mMockContentProtectionAllowlistManager;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
new file mode 100644
index 0000000..6767a85
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionAllowlistManager}.
+ *
+ * <p>Run with: {@code atest FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionAllowlistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionAllowlistManagerTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
+
+ @Before
+ public void setup() {
+ mContentProtectionAllowlistManager = new ContentProtectionAllowlistManager();
+ }
+
+ @Test
+ public void isAllowed() {
+ boolean actual = mContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
deleted file mode 100644
index ba9956a..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test for {@link ContentProtectionBlocklistManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:
- * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionBlocklistManagerTest {
-
- private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
-
- private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
-
- private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- private static final PackageInfo PACKAGE_INFO = new PackageInfo();
-
- private static final List<String> LINES =
- ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
-
- private final List<String> mReadRawFiles = new ArrayList<>();
-
- private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
-
- @Before
- public void setup() {
- mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
- }
-
- @Test
- public void isAllowed_blocklistNotLoaded() {
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- assertThat(mReadRawFiles).isEmpty();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_inBlocklist() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_packageInfoNotFound() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(null);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never())
- .hasRequestedInternetPermissions(any());
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_notRequestedInternet() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_systemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_updatedSystemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isAllowed_allowed() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void updateBlocklist_negativeSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_zeroSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_belowTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_aboveTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- private final class TestContentProtectionBlocklistManager
- extends ContentProtectionBlocklistManager {
-
- TestContentProtectionBlocklistManager() {
- super(mMockContentProtectionPackageManager);
- }
-
- @Override
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- mReadRawFiles.add(filename);
- return LINES;
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
deleted file mode 100644
index 7d45ea4..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.Manifest.permission;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.testing.TestableContext;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Test for {@link ContentProtectionPackageManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionPackageManagerTest {
- private static final String PACKAGE_NAME = "PACKAGE_NAME";
-
- private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
-
- private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
-
- private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
- createUpdatedSystemAppPackageInfo();
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final TestableContext mContext =
- new TestableContext(ApplicationProvider.getApplicationContext());
-
- @Mock private PackageManager mMockPackageManager;
-
- private ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Before
- public void setup() {
- mContext.setMockPackageManager(mMockPackageManager);
- mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
- }
-
- @Test
- public void getPackageInfo_found() throws Exception {
- PackageInfo expected = createPackageInfo(/* flags= */ 0);
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenReturn(expected);
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- public void getPackageInfo_notFound() throws Exception {
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenThrow(new NameNotFoundException());
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void getPackageInfo_null() {
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void isSystemApp_true() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_true() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(
- UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isUpdatedSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_true() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void hasRequestedInternetPermissions_false() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_noRequestedPermissions() {
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(
- EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- private static PackageInfo createSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
- }
-
- private static PackageInfo createUpdatedSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
- }
-
- private static PackageInfo createPackageInfo(int flags) {
- return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
- }
-
- private static PackageInfo createPackageInfo(String[] requestedPermissions) {
- return createPackageInfo(/* flags= */ 0, requestedPermissions);
- }
-
- private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
- PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo = new ApplicationInfo();
- packageInfo.applicationInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo.flags = flags;
- packageInfo.requestedPermissions = requestedPermissions;
- return packageInfo;
- }
-}
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 fe2ac17..f5d50d1 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 @@
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 91f3fed..c08ad13 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 @@
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 @@
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/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 9f6d7f2..ece3dfe 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,11 +26,10 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -69,7 +68,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
@@ -664,6 +662,39 @@
verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource);
}
+ @Test
+ public void notifyPermissionRequestDisplayed_forwardsToLogger() {
+ int hostUid = 123;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyPermissionRequestDisplayed(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid);
+ }
+
+ @Test
+ public void notifyPermissionRequestCancelled_forwardsToLogger() {
+ int hostUid = 123;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyPermissionRequestCancelled(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logProjectionPermissionRequestCancelled(hostUid);
+ }
+
+ @Test
+ public void notifyAppSelectorDisplayed_forwardsToLogger() {
+ int hostUid = 456;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyAppSelectorDisplayed(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logAppSelectorDisplayed(hostUid);
+ }
+
/**
* Executes and validates scenario where the consent result indicates the projection ends.
*/
@@ -837,10 +868,69 @@
service.setContentRecordingSession(DISPLAY_SESSION);
- verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+ verify(mMediaProjectionMetricsLogger).logInProgress(
projection.uid,
- MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+ DISPLAY_SESSION.getTargetUid()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_taskSession_logsCaptureInProgressWithTargetUid()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true)
+ .when(mWindowManagerInternal)
+ .setContentRecordingSession(any(ContentRecordingSession.class));
+ int targetUid = 123455;
+
+ ContentRecordingSession taskSession =
+ ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+ service.setContentRecordingSession(taskSession);
+
+ verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid);
+ }
+
+ @Test
+ public void setContentRecordingSession_failure_doesNotLogCaptureInProgress() throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionNull_doesNotLogCaptureInProgress()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(null);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
index 73b4cc8..ad1cd6e 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -17,13 +17,17 @@
package com.android.server.media.projection;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,7 +41,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -216,21 +219,295 @@
}
@Test
- public void logStopped_registersActiveSessionEnded_afterLogging() {
+ public void logStopped_capturingWasInProgress_registersActiveSessionEnded() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
- InOrder inOrder = inOrder(mFrameworkStatsLogWrapper, mTimestampStore);
- inOrder.verify(mFrameworkStatsLogWrapper)
- .write(
- /* code= */ anyInt(),
- /* sessionId= */ anyInt(),
- /* state= */ anyInt(),
- /* previousState= */ anyInt(),
- /* hostUid= */ anyInt(),
- /* targetUid= */ anyInt(),
- /* timeSinceLastActive= */ anyInt(),
- /* creationSource= */ anyInt());
- inOrder.verify(mTimestampStore).registerActiveSessionEnded();
+ verify(mTimestampStore).registerActiveSessionEnded();
+ }
+
+ @Test
+ public void logStopped_capturingWasNotInProgress_doesNotRegistersActiveSessionEnded() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verify(mTimestampStore, never()).registerActiveSessionEnded();
+ }
+
+ @Test
+ public void logInProgress_logsStateChangedAtomId() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logInProgress_logsCurrentSessionId() {
+ int currentSessionId = 987;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logInProgress_logsStateInProgress() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+ }
+
+ @Test
+ public void logInProgress_logsHostUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logInProgress_logsTargetUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTargetUidLogged(TEST_TARGET_UID);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownTimeSinceLastActive() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownSessionCreationSource() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logInProgress_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateChangedAtomId() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsCurrentSessionId() {
+ int currentSessionId = 765;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateDisplayed() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsHostUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTargetUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTimeSinceLastActive() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownSessionCreationSource() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsStateChangedAtomId() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsCurrentSessionId() {
+ int currentSessionId = 765;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsStateDisplayed() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsHostUid() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownTargetUid() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownTimeSinceLastActive() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownSessionCreationSource() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsStateChangedAtomId() {
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsExistingSessionId() {
+ int existingSessionId = 456;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(existingSessionId);
+
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifySessionIdLogged(existingSessionId);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsStateCancelled() {
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsHostUid() {
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsUnknownTargetUid() {
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logProjectionPermissionRequestCancelled_logsUnknownCreationSource() {
+ mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
}
private void verifyStateChangedAtomIdLogged() {
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index fc27edc..a4d50f0 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -94,27 +94,31 @@
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile);
+ callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
assertThrows(IllegalArgumentException.class, () ->
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- callingInfo, "unknown-file.zip"));
+ mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+ "unknown-file.zip"));
// No exception should be thrown.
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile);
}
@Test
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile);
+ callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile2);
+ callingInfo, mBugreportFile2, /* keepOnRetrieval= */ false);
// No exception should be thrown.
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile2);
}
@Test
@@ -122,7 +126,8 @@
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
assertThrows(IllegalArgumentException.class,
() -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- callingInfo, "test-file.zip"));
+ mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+ "test-file.zip"));
}
@Test
@@ -130,7 +135,8 @@
CountDownLatch latch = new CountDownLatch(1);
Listener listener = new Listener(latch);
mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
- new FileDescriptor(), mBugreportFile, listener);
+ mContext.getUserId(), new FileDescriptor(), mBugreportFile,
+ /* keepOnRetrieval= */ false, listener);
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
assertThat(listener.getErrorCode()).isEqualTo(
BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5dfce06..89c6a220 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -974,7 +974,6 @@
assertThrows(SecurityException.class, userProps::getAlwaysVisible);
}
-
// Make sure only max managed profiles can be created
@MediumTest
@Test
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 9fca513..d09aa89 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.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.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.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 @@
* 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 @@
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 @@
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 @@
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 @@
});
latch2.await();
- assumeTrue(a.updateHintAllowed());
+ assertTrue(service.mUidObserver.isUidForeground(a.mUid));
verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
}
@@ -254,7 +320,7 @@
});
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 @@
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 @@
});
latch.await();
- assertFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
}
@Test
@@ -316,7 +382,7 @@
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 @@
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 @@
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 51b9c17..47f15b8 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 @@
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 @@
}
@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/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 61c4d06..270d5df 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -203,5 +203,6 @@
mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
}
}
+ mPhoneWindowManager.dispatchAllPendingEvents();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index d388db8..f2721a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -25,10 +25,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Looper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -38,7 +40,9 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -57,6 +61,7 @@
private CountDownLatch mLongPressed = new CountDownLatch(1);
private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
private CountDownLatch mMultiPressed = new CountDownLatch(1);
+ private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>();
private final Instrumentation mInstrumentation = getInstrumentation();
private final Context mContext = mInstrumentation.getTargetContext();
@@ -76,7 +81,7 @@
public void setUp() {
mInstrumentation.runOnMainSync(
() -> {
- mDetector = SingleKeyGestureDetector.get(mContext);
+ mDetector = SingleKeyGestureDetector.get(mContext, Looper.myLooper());
initSingleKeyGestureRules();
});
@@ -134,6 +139,11 @@
assertTrue(mMaxMultiPressCount >= count);
assertEquals(mExpectedMultiPressCount, count);
}
+
+ @Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
+ }
});
mDetector.addRule(
@@ -167,12 +177,27 @@
}
@Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
+ }
+
+ @Override
void onLongPress(long downTime) {
mLongPressed.countDown();
}
});
}
+ private static class KeyUpData {
+ public final int keyCode;
+ public final int pressCount;
+
+ KeyUpData(int keyCode, int pressCount) {
+ this.keyCode = keyCode;
+ this.pressCount = pressCount;
+ }
+ }
+
private void pressKey(int keyCode, long pressTime) {
pressKey(keyCode, pressTime, true /* interactive */);
}
@@ -250,6 +275,21 @@
}
@Test
+ public void testOnKeyUp() throws InterruptedException {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ }
+
+ private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)
+ throws InterruptedException {
+ KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS);
+ assertNotNull(keyUpData);
+ assertEquals(expectedKeyCode, keyUpData.keyCode);
+ assertEquals(expectedMultiPressCount, keyUpData.pressCount);
+ }
+
+ @Test
public void testNonInteractive() throws InterruptedException {
// Disallow short press behavior from non interactive.
mAllowNonInteractiveForPress = false;
@@ -314,6 +354,33 @@
}
@Test
+ public void testOnKeyUp_Pressure() throws InterruptedException {
+ final HandlerThread handlerThread =
+ new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
+ handlerThread.start();
+ Handler newHandler = new Handler(handlerThread.getLooper());
+ try {
+ // To make sure we won't get any unexpected multi-press count.
+ for (int i = 0; i < 5; i++) {
+ newHandler.runWithScissors(
+ () -> {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ },
+ mWaitTimeout);
+ newHandler.runWithScissors(
+ () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */);
+ }
+ } finally {
+ handlerThread.quitSafely();
+ }
+ }
+
+ @Test
public void testUpdateRule() throws InterruptedException {
// Power key rule doesn't allow the long press gesture.
mLongPressOnPowerBehavior = false;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 261d3cc..e26260a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -118,7 +118,6 @@
import java.util.function.Supplier;
class TestPhoneWindowManager {
- private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
private static final long TEST_SINGLE_KEY_DELAY_MILLIS
= SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
@@ -188,7 +187,7 @@
MockitoAnnotations.initMocks(this);
mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mHandler.post(() -> setUp(supportSettingsUpdate));
+ setUp(supportSettingsUpdate);
mTestLooper.dispatchAll();
}
@@ -306,6 +305,10 @@
mMockitoSession.finishMocking();
}
+ void dispatchAllPendingEvents() {
+ mTestLooper.dispatchAll();
+ }
+
// Override accessibility setting and perform function.
private void overrideLaunchAccessibility() {
doReturn(true).when(mAccessibilityShortcutController)
@@ -446,6 +449,7 @@
doNothing().when(mPhoneWindowManager).sendCloseSystemWindows();
doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+ doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class));
}
void overrideSearchManager(SearchManager searchManager) {
@@ -500,29 +504,24 @@
*/
void assertTakeScreenshotCalled() {
mTestLooper.dispatchAll();
- verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .takeScreenshot(anyInt(), anyInt());
+ verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
void assertShowGlobalActionsCalled() {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
- verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .showDialog(anyBoolean(), anyBoolean());
- verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .userActivity(anyLong(), anyBoolean());
+ verify(mGlobalActions).showDialog(anyBoolean(), anyBoolean());
+ verify(mPowerManager).userActivity(anyLong(), anyBoolean());
}
void assertVolumeMute() {
mTestLooper.dispatchAll();
- verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .silenceRingerModeInternal(eq("volume_hush"));
+ verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
}
void assertAccessibilityKeychordCalled() {
mTestLooper.dispatchAll();
- verify(mAccessibilityShortcutController,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
+ verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
void assertDreamRequest() {
@@ -532,14 +531,12 @@
void assertPowerSleep() {
mTestLooper.dispatchAll();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
+ verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
+ verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
}
void assertNoPowerSleep() {
@@ -556,7 +553,7 @@
void assertSearchManagerLaunchAssist() {
mTestLooper.dispatchAll();
- verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
+ verify(mSearchManager).launchAssist(any());
}
void assertLaunchCategory(String category) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 40ac7b1c..bdbfb7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2627,6 +2627,13 @@
// Can specify orientation if the current orientation candidate is orientation behind.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
+ .setCreateTask(true).build();
+ assertFalse(translucentActivity.providesOrientation());
+ translucentActivity.setOccludesParent(true);
+ assertTrue(translucentActivity.providesOrientation());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 6a738be..9f58491 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -41,7 +41,6 @@
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.utils.MockAnimationAdapter;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -150,7 +149,7 @@
mHost = new MockSurfaceBuildingContainer(mWm);
mTransaction = spy(StubTransaction.class);
mChild = new TestWindowContainer(mWm);
- if (Flags.dimmerRefactor()) {
+ if (Dimmer.DIMMER_REFACTOR) {
sTestAnimation = spy(new MockAnimationAdapter());
mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
} else {
@@ -177,7 +176,7 @@
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
final int blur = 50;
mHost.addChild(mChild, 0);
@@ -197,7 +196,7 @@
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, alpha, 20);
@@ -212,7 +211,7 @@
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -232,7 +231,7 @@
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -292,7 +291,7 @@
@Test
public void testRemoveDimImmediately_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 2);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -312,7 +311,7 @@
@Test
public void testRemoveDimImmediately_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 0);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -332,7 +331,7 @@
@Test
public void testDimmerWithBlurUpdatesTransaction_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final int blurRadius = 50;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5f92fd5..0d4c443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -676,60 +676,59 @@
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE),
- SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -740,8 +739,8 @@
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -760,8 +759,8 @@
doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -780,8 +779,8 @@
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -789,8 +788,8 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -799,8 +798,8 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -808,8 +807,8 @@
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -817,15 +816,15 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LOCKED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LOCKED));
// unchanged if orientation is specified
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@@ -833,8 +832,8 @@
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
// shouldApplyUser...Override
@@ -1286,16 +1285,16 @@
mActivity = setUpActivityWithComponent();
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), 1.5f, /* delta */ 0.01);
+ assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isTreatmentEnabledForActivity(eq(mActivity));
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), mController.getSplitScreenAspectRatio(),
- /* delta */ 0.01);
+ assertEquals(mController.getSplitScreenAspectRatio(),
+ mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
}
private void mockThatProperty(String propertyName, boolean value) throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 2597465..03188f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -208,15 +208,18 @@
private void setUpSystemCore() {
doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
doAnswer(invocation -> {
- // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
- // only registers once and it doesn't reference to outside.
- if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
- mDeviceConfigListeners.add(invocation.getArgument(2));
+ if ("addOnPropertiesChangedListener".equals(invocation.getMethod().getName())) {
+ // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
+ // only registers once and it doesn't reference to outside.
+ if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
+ mDeviceConfigListeners.add(invocation.getArgument(2));
+ }
+ // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
+ // uses splash_screen_exception_list. So still execute real registration.
}
- // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
- // uses splash_screen_exception_list. So still execute real registration.
return invocation.callRealMethod();
- }).when(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(), any(), any()));
+ }).when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(), any(DeviceConfig.OnPropertiesChangedListener.class)));
mContext = getInstrumentation().getTargetContext();
spyOn(mContext);
@@ -397,20 +400,24 @@
}
private void tearDown() {
- for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
- final DisplayContent dc = mWmService.mRoot.getChildAt(i);
- // Unregister SettingsObserver.
- dc.getDisplayPolicy().release();
- // Unregister SensorEventListener (foldable device may register for hinge angle).
- dc.getDisplayRotation().onDisplayRemoved();
- if (dc.mDisplayRotationCompatPolicy != null) {
- dc.mDisplayRotationCompatPolicy.dispose();
+ if (mWmService != null) {
+ for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent dc = mWmService.mRoot.getChildAt(i);
+ // Unregister SettingsObserver.
+ dc.getDisplayPolicy().release();
+ // Unregister SensorEventListener (foldable device may register for hinge angle).
+ dc.getDisplayRotation().onDisplayRemoved();
+ if (dc.mDisplayRotationCompatPolicy != null) {
+ dc.mDisplayRotationCompatPolicy.dispose();
+ }
}
}
- // Unregister display listener from root to avoid issues with subsequent tests.
- mContext.getSystemService(DisplayManager.class)
- .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+ if (mAtmService != null) {
+ // Unregister display listener from root to avoid issues with subsequent tests.
+ mContext.getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+ }
for (int i = mDeviceConfigListeners.size() - 1; i >= 0; i--) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListeners.get(i));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 6235b3b..c57b051 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -1742,6 +1742,7 @@
private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
@TaskFragmentOperation.OperationType int opType) {
final Task task = createTask(mDisplayContent);
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
// Create a non-embedded Activity at the bottom.
final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
.setTask(task)
@@ -1934,7 +1935,7 @@
/** Setups the mock Task as the parent of the given TaskFragment. */
private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
doReturn(mockParent).when(taskFragment).getTask();
- doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true))
+ doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true))
.when(mockParent).getTaskFragmentParentInfo();
// Task needs to be visible
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 13a4c11..8fecbb9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -96,7 +96,7 @@
@Test
public void testTaskRemovedFromRecents() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
- mPersister.onTaskRemovedFromRecents(1, mTestUserId);
+ mPersister.removeSnapshot(1, mTestUserId);
mSnapshotPersistQueue.waitForQueueEmpty();
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists());
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 08438c8..267bec9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -19,6 +19,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -79,7 +80,7 @@
private ImageReader mImageReader;
private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
- private static final int WAIT_TIMEOUT_MS = 5000;
+ private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER;
private static final Object sLock = new Object();
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 0c58069..435a835 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1568,6 +1568,7 @@
final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
task.moveActivityToFront(activity);
assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
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 c8546c6..4773023 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 @@
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 0b77fd8..699580a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -101,6 +101,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.function.BiConsumer;
/**
* Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}.
@@ -583,7 +584,7 @@
}
@Test
- public void testTaskFragmentHiddenAndFocusableChanges() {
+ public void testTaskFragmentHiddenFocusableTranslucentChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
@@ -605,10 +606,12 @@
assertTrue(taskFragment.shouldBeVisible(null));
assertTrue(taskFragment.isFocusable());
assertTrue(taskFragment.isTopActivityFocusable());
+ assertFalse(taskFragment.isForceTranslucent());
// Apply transaction to the TaskFragment hidden and not focusable.
t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
@@ -617,10 +620,12 @@
assertFalse(taskFragment.shouldBeVisible(null));
assertFalse(taskFragment.isFocusable());
assertFalse(taskFragment.isTopActivityFocusable());
+ assertTrue(taskFragment.isForceTranslucent());
// Apply transaction to the TaskFragment not hidden and focusable.
t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
@@ -629,10 +634,32 @@
assertTrue(taskFragment.shouldBeVisible(null));
assertTrue(taskFragment.isFocusable());
assertTrue(taskFragment.isTopActivityFocusable());
+ assertFalse(taskFragment.isForceTranslucent());
}
@Test
- public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+ public void testTaskFragmentChangeHidden_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the hidden state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setHidden(windowContainerToken, true));
+ }
+
+ @Test
+ public void testTaskFragmentChangeFocusable_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the focusable state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setFocusable(windowContainerToken, false));
+ }
+
+ @Test
+ public void testTaskFragmentChangeTranslucent_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the translucent state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setForceTranslucent(windowContainerToken, true));
+ }
+
+ private void testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ BiConsumer<WindowContainerTransaction, WindowContainerToken> addOp) {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
@@ -641,21 +668,15 @@
final TaskFragmentOrganizer organizer =
createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
- final IBinder token = new Binder();
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(rootTask)
- .setFragmentToken(token)
+ .setFragmentToken(new Binder())
.setOrganizer(organizer)
.createActivityCount(1)
.build();
- assertTrue(rootTask.shouldBeVisible(null));
- assertTrue(taskFragment.shouldBeVisible(null));
+ addOp.accept(t, taskFragment.mRemoteToken.toWindowContainerToken());
- t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
- t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
-
- // Non-system organizers are not allow to update the hidden and focusable states.
assertThrows(SecurityException.class, () ->
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
@@ -1646,6 +1667,28 @@
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/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 9146889..e0ed642 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1184,6 +1184,7 @@
private boolean mCreateTask = false;
private Task mParentTask;
private int mActivityFlags;
+ private int mActivityTheme;
private int mLaunchMode;
private int mResizeMode = RESIZE_MODE_RESIZEABLE;
private float mMaxAspectRatio;
@@ -1232,6 +1233,14 @@
return this;
}
+ ActivityBuilder setActivityTheme(int theme) {
+ mActivityTheme = theme;
+ // Use the real package of test so it can get a valid context for theme.
+ mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
+ DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+ return this;
+ }
+
ActivityBuilder setActivityFlags(int flags) {
mActivityFlags = flags;
return this;
@@ -1381,6 +1390,9 @@
if (mTargetActivity != null) {
aInfo.targetActivity = mTargetActivity;
}
+ if (mActivityTheme != 0) {
+ aInfo.theme = mActivityTheme;
+ }
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
aInfo.resizeMode = mResizeMode;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 3ec6f42..973ab84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -448,7 +448,6 @@
mDisplayContent.updateImeParent();
// Ime should on top of the popup IME layering target window.
- mDisplayContent.assignChildLayers(mTransaction);
assertWindowHigher(mImeWindow, popupImeTargetWin);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 93b5a40..f6c6a64 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 @@
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_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.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 @@
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 @@
}
@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 @@
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 @@
}
/**
+ * 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 9a4fbdc..6418f3e 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.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 @@
}
}
}
+
+ @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 0a70a5f..b098e82 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 @@
new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY);
-
mLastRestartInstant = Instant.now();
if (mReStartPeriodSeconds <= 0) {
@@ -918,7 +917,8 @@
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 f06c997..2e23eff 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.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 @@
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 1c689d0..a584fc9 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 @@
}
}
}
- //----------------- 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 6ba77da..3c4b58f 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.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.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 @@
}
}
+ @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 e172090..0000000
--- 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 e5aead9..0000000
--- 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 f8da02b..0000000
--- 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 5f5652c..0000000
--- 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 03bd545..0000000
--- 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 50cf5a50..0000000
--- 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 eb2dc88..0000000
--- 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 bcba2fe..0000000
--- 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 b335663..0000000
--- 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 59093c7..0000000
--- 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 0f9375c..0000000
--- 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 b092e1c..0000000
--- 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 6af01f6f..0000000
--- 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 b0cf91d..0000000
--- 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 dd77923..0000000
--- 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 bddb8aa..0000000
--- 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 a34ed1f..0000000
--- 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 5dda59e..0000000
--- 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 920caee..0000000
--- 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 69356d3..0000000
--- 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 8c77377..0000000
--- 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 bed34bb..0000000
--- 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 b74cdae..0000000
--- 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 11ecde2..0000000
--- 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.cc b/startop/view_compiler/tinyxml_layout_parser.cc
deleted file mode 100644
index 1b3a81f..0000000
--- a/startop/view_compiler/tinyxml_layout_parser.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 "tinyxml_layout_parser.h"
-
-#include "layout_validation.h"
-
-namespace startop {
-
-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
diff --git a/startop/view_compiler/tinyxml_layout_parser.h b/startop/view_compiler/tinyxml_layout_parser.h
deleted file mode 100644
index 8f714a2..0000000
--- 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 c34d7b0..0000000
--- 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.h b/startop/view_compiler/util.h
deleted file mode 100644
index 0176175..0000000
--- a/startop/view_compiler/util.h
+++ /dev/null
@@ -1,29 +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 VIEW_COMPILER_UTIL_H_
-#define VIEW_COMPILER_UTIL_H_
-
-#include <string>
-
-namespace startop {
-namespace util {
-
-std::string FindLayoutNameFromFilename(const std::string& filename);
-
-} // namespace util
-} // namespace startop
-
-#endif // VIEW_COMPILER_UTIL_H_
diff --git a/startop/view_compiler/util_test.cc b/startop/view_compiler/util_test.cc
deleted file mode 100644
index 50682a0..0000000
--- 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 540cecf..042b2a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3148,6 +3148,14 @@
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 @@
* 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 @@
* {@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 @@
/**
* 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";
@@ -9681,7 +9692,6 @@
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
- * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -10194,6 +10204,7 @@
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 cee2efb..f161f31 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.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 @@
* @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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 73220c3..c0d6b30 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17483,6 +17483,8 @@
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+ *
+ * @hide
*/
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index ecd7039..d30078d 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -29,6 +29,7 @@
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.util.ArrayList;
@@ -545,6 +546,11 @@
try {
iSession.setListener(mIImsCallSessionListenerProxy);
} catch (RemoteException e) {
+ if (Flags.ignoreAlreadyTerminatedIncomingCallBeforeRegisteringListener()) {
+ // Registering listener failed, so other operations are not allowed.
+ Log.e(TAG, "ImsCallSession : " + e);
+ mClosed = true;
+ }
}
} else {
mClosed = true;
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 6b47db1..e2cd4f8 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -469,6 +469,9 @@
* 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 @@
* 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/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 158e065..dc55dd2 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -237,6 +237,12 @@
*/
private transient LinkAddress[] mLinkAddresses6;
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ * @hide
+ */
+ private @NonNull List<LinkAddress> mLinkAddresses7 = new ArrayList<>();
+
+ /**
* When using transient fields for caching it's often also a good idea to initialize them
* lazily.
*
@@ -486,6 +492,8 @@
* Making a field public will suppress getter generation in favor of accessing it directly.
* @param linkAddresses5
* Final fields suppress generating a setter (when setters are requested).
+ * @param linkAddresses7
+ * For hidden lists, getters, setters and adders will be hidden.
* @param stringRes
* Fields with certain annotations are automatically validated in constructor
*
@@ -529,9 +537,10 @@
@State int state,
@NonNull CharSequence charSeq,
@Nullable LinkAddress[] linkAddresses5,
+ @NonNull List<LinkAddress> linkAddresses7,
@StringRes int stringRes,
@android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
- @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+ @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] coords,
@NonNull IBinder token,
@Nullable ICompanionDeviceManager iPCInterface) {
this.mNum = num;
@@ -595,6 +604,9 @@
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -609,13 +621,11 @@
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -777,6 +787,16 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<LinkAddress> getLinkAddresses7() {
+ return mLinkAddresses7;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -815,7 +835,7 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @Size(2) @NonNull @FloatRange(from = 0f) float[] getCoords() {
+ public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
return mCoords;
}
@@ -1065,6 +1085,19 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ mLinkAddresses7 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1111,20 +1144,18 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
mCoords = value;
AnnotationValidations.validate(
Size.class, null, mCoords.length,
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
return this;
}
@@ -1172,6 +1203,7 @@
"state = " + stateToString(mState) + ", " +
"charSeq = " + charSeq + ", " +
"linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+ "linkAddresses7 = " + mLinkAddresses7 + ", " +
"stringRes = " + mStringRes + ", " +
"dayOfWeek = " + mDayOfWeek + ", " +
"coords = " + java.util.Arrays.toString(mCoords) + ", " +
@@ -1210,6 +1242,7 @@
&& mState == that.mState
&& Objects.equals(charSeq, that.charSeq)
&& java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+ && Objects.equals(mLinkAddresses7, that.mLinkAddresses7)
&& mStringRes == that.mStringRes
&& mDayOfWeek == that.mDayOfWeek
&& java.util.Arrays.equals(mCoords, that.mCoords)
@@ -1241,6 +1274,7 @@
_hash = 31 * _hash + mState;
_hash = 31 * _hash + Objects.hashCode(charSeq);
_hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+ _hash = 31 * _hash + Objects.hashCode(mLinkAddresses7);
_hash = 31 * _hash + mStringRes;
_hash = 31 * _hash + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
@@ -1270,6 +1304,7 @@
actionInt.acceptInt(this, "state", mState);
actionObject.acceptObject(this, "charSeq", charSeq);
actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ actionObject.acceptObject(this, "linkAddresses7", mLinkAddresses7);
actionInt.acceptInt(this, "stringRes", mStringRes);
actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
@@ -1298,6 +1333,7 @@
action.acceptObject(this, "state", mState);
action.acceptObject(this, "charSeq", charSeq);
action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ action.acceptObject(this, "linkAddresses7", mLinkAddresses7);
action.acceptObject(this, "stringRes", mStringRes);
action.acceptObject(this, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
@@ -1338,7 +1374,7 @@
if (mOtherParcelable != null) flg |= 0x40;
if (mLinkAddresses4 != null) flg |= 0x800;
if (mLinkAddresses5 != null) flg |= 0x10000;
- if (mIPCInterface != null) flg |= 0x200000;
+ if (mIPCInterface != null) flg |= 0x400000;
dest.writeLong(flg);
dest.writeInt(mNum);
dest.writeInt(mNum2);
@@ -1357,6 +1393,7 @@
dest.writeInt(mState);
dest.writeCharSequence(charSeq);
if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+ dest.writeParcelableList(mLinkAddresses7, flags);
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
@@ -1395,11 +1432,13 @@
int state = in.readInt();
CharSequence _charSeq = (CharSequence) in.readCharSequence();
LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+ List<LinkAddress> linkAddresses7 = new ArrayList<>();
+ in.readParcelableList(linkAddresses7, LinkAddress.class.getClassLoader());
int stringRes = in.readInt();
int dayOfWeek = in.readInt();
float[] coords = in.createFloatArray();
IBinder token = (IBinder) in.readStrongBinder();
- ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
+ ICompanionDeviceManager iPCInterface = (flg & 0x400000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
this.mNum = num;
this.mNum2 = num2;
@@ -1462,6 +1501,9 @@
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -1476,13 +1518,11 @@
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -1529,9 +1569,10 @@
private @State int mState;
private @NonNull CharSequence charSeq;
private @Nullable LinkAddress[] mLinkAddresses5;
+ private @NonNull List<LinkAddress> mLinkAddresses7;
private @StringRes int mStringRes;
private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
- private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+ private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
private @NonNull IBinder mToken;
private @Nullable ICompanionDeviceManager mIPCInterface;
@@ -1823,6 +1864,30 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20000;
+ mLinkAddresses7 = value;
+ return this;
+ }
+
+ /** @see #setLinkAddresses7 @hide */
+ @DataClass.Generated.Member
+ public @NonNull Builder addLinkAddresses7(@NonNull LinkAddress value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mLinkAddresses7 == null) setLinkAddresses7(new ArrayList<>());
+ mLinkAddresses7.add(value);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1837,7 +1902,7 @@
@DataClass.Generated.Member
public @NonNull Builder setStringRes(@StringRes int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20000;
+ mBuilderFieldsSet |= 0x40000;
mStringRes = value;
return this;
}
@@ -1852,7 +1917,7 @@
@DataClass.Generated.Member
public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x40000;
+ mBuilderFieldsSet |= 0x80000;
mDayOfWeek = value;
return this;
}
@@ -1867,9 +1932,9 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull Builder setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x80000;
+ mBuilderFieldsSet |= 0x100000;
mCoords = value;
return this;
}
@@ -1880,7 +1945,7 @@
@DataClass.Generated.Member
public @NonNull Builder setToken(@NonNull IBinder value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000;
+ mBuilderFieldsSet |= 0x200000;
mToken = value;
return this;
}
@@ -1891,7 +1956,7 @@
@DataClass.Generated.Member
public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x200000;
+ mBuilderFieldsSet |= 0x400000;
mIPCInterface = value;
return this;
}
@@ -1899,7 +1964,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull SampleDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400000; // Mark builder used
+ mBuilderFieldsSet |= 0x800000; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
@@ -1935,18 +2000,21 @@
charSeq = "";
}
if ((mBuilderFieldsSet & 0x20000) == 0) {
- mStringRes = 0;
+ mLinkAddresses7 = new ArrayList<>();
}
if ((mBuilderFieldsSet & 0x40000) == 0) {
- mDayOfWeek = 3;
+ mStringRes = 0;
}
if ((mBuilderFieldsSet & 0x80000) == 0) {
- mCoords = new float[] { 0f, 0f };
+ mDayOfWeek = 3;
}
if ((mBuilderFieldsSet & 0x100000) == 0) {
- mToken = new Binder();
+ mCoords = new float[] { 0f, 0f };
}
if ((mBuilderFieldsSet & 0x200000) == 0) {
+ mToken = new Binder();
+ }
+ if ((mBuilderFieldsSet & 0x400000) == 0) {
mIPCInterface = null;
}
SampleDataClass o = new SampleDataClass(
@@ -1967,6 +2035,7 @@
mState,
charSeq,
mLinkAddresses5,
+ mLinkAddresses7,
mStringRes,
mDayOfWeek,
mCoords,
@@ -1976,7 +2045,7 @@
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400000) != 0) {
+ if ((mBuilderFieldsSet & 0x800000) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -1984,10 +2053,10 @@
}
@DataClass.Generated(
- time = 1616541539978L,
+ time = 1697693846352L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
- inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses7\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/notification/Android.bp b/tests/notification/Android.bp
deleted file mode 100644
index 1c1b5a2..0000000
--- 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 7cee00a7..0000000
--- 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 396fd12..0000000
--- 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
deleted file mode 100644
index c6d8ae9..0000000
--- a/tests/notification/res/drawable-nodpi/arubin_hed.jpeg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/bucket.png b/tests/notification/res/drawable-nodpi/bucket.png
deleted file mode 100644
index c865649..0000000
--- a/tests/notification/res/drawable-nodpi/bucket.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/matias_hed.jpg b/tests/notification/res/drawable-nodpi/matias_hed.jpg
deleted file mode 100644
index 8cc3081..0000000
--- a/tests/notification/res/drawable-nodpi/matias_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/page_hed.jpg b/tests/notification/res/drawable-nodpi/page_hed.jpg
deleted file mode 100644
index ea950c8..0000000
--- a/tests/notification/res/drawable-nodpi/page_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg b/tests/notification/res/drawable-nodpi/romainguy_hed.jpg
deleted file mode 100644
index 5b7643e..0000000
--- a/tests/notification/res/drawable-nodpi/romainguy_hed.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg b/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg
deleted file mode 100644
index 68473ba..0000000
--- a/tests/notification/res/drawable-nodpi/romainguy_rockaway.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/add.png b/tests/notification/res/drawable-xhdpi/add.png
deleted file mode 100644
index 7226b3d..0000000
--- a/tests/notification/res/drawable-xhdpi/add.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png b/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png
deleted file mode 100644
index ca20a91..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_dial_action_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_end_call.png b/tests/notification/res/drawable-xhdpi/ic_end_call.png
deleted file mode 100644
index c464a6d..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_end_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_media_next.png b/tests/notification/res/drawable-xhdpi/ic_media_next.png
deleted file mode 100644
index 4def965..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_media_next.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png b/tests/notification/res/drawable-xhdpi/ic_menu_upload.png
deleted file mode 100644
index f1438ed..0000000
--- a/tests/notification/res/drawable-xhdpi/ic_menu_upload.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/icon.png b/tests/notification/res/drawable-xhdpi/icon.png
deleted file mode 100644
index 189e85b..0000000
--- a/tests/notification/res/drawable-xhdpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png b/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png
deleted file mode 100644
index 658d04f..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_alarm.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png b/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png
deleted file mode 100644
index 5ae7782..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_calendar.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_email.png b/tests/notification/res/drawable-xhdpi/stat_notify_email.png
deleted file mode 100644
index 23c4672..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png b/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png
deleted file mode 100644
index 8719eff..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_missed_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png b/tests/notification/res/drawable-xhdpi/stat_notify_sms.png
deleted file mode 100644
index 323cb3d..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_sms.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png
deleted file mode 100644
index 26dcda35..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png b/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png
deleted file mode 100644
index b8b2f8a..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_snooze_longer.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png b/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png
deleted file mode 100644
index 12cae9f..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_notify_talk_text.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png b/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png
deleted file mode 100644
index db42b7c..0000000
--- a/tests/notification/res/drawable-xhdpi/stat_sys_phone_call.png
+++ /dev/null
Binary files differ
diff --git a/tests/notification/res/layout/full_screen.xml b/tests/notification/res/layout/full_screen.xml
deleted file mode 100644
index 6ff7552..0000000
--- 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 f5a740f..0000000
--- 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 21e7bc3..0000000
--- 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 80bf103..0000000
--- 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 5d639f6..0000000
--- 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;
- }
- }
-}
-
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index a1a39ff..359ef83 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -125,6 +125,17 @@
TUNNEL_CONNECTION_PARAMS);
}
+ private static VcnGatewayConnectionConfig.Builder newBuilderMinimal() {
+ final VcnGatewayConnectionConfig.Builder builder =
+ new VcnGatewayConnectionConfig.Builder(
+ "newBuilderMinimal", TUNNEL_CONNECTION_PARAMS);
+ for (int caps : EXPOSED_CAPS) {
+ builder.addExposedCapability(caps);
+ }
+
+ return builder;
+ }
+
private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions(
VcnGatewayConnectionConfig.Builder builder,
Set<Integer> gatewayOptions,
@@ -273,6 +284,7 @@
assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
assertEquals(MAX_MTU, config.getMaxMtu());
+ assertTrue(config.isSafeModeEnabled());
assertFalse(
config.hasGatewayOption(
@@ -290,6 +302,13 @@
}
@Test
+ public void testBuilderAndGettersSafeModeDisabled() {
+ final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+
+ assertFalse(config.isSafeModeEnabled());
+ }
+
+ @Test
public void testPersistableBundle() {
final VcnGatewayConnectionConfig config = buildTestConfig();
@@ -305,6 +324,13 @@
}
@Test
+ public void testPersistableBundleSafeModeDisabled() {
+ final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+
+ assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
+ }
+
+ @Test
public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
@@ -411,4 +437,18 @@
assertEquals(config, configEqual);
assertNotEquals(config, configNotEqual);
}
+
+ @Test
+ public void testSafeModeEnableDisableEquality() throws Exception {
+ final VcnGatewayConnectionConfig config = newBuilderMinimal().build();
+ final VcnGatewayConnectionConfig configEqual = newBuilderMinimal().build();
+
+ assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled());
+
+ final VcnGatewayConnectionConfig configNotEqual =
+ newBuilderMinimal().enableSafeMode(false).build();
+
+ assertEquals(config, configEqual);
+ assertNotEquals(config, configNotEqual);
+ }
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 1b1e93bd..a08f385 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -323,6 +323,7 @@
"should only be used together with the --static-lib flag.",
&options_.merge_only);
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
+ AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
}
int Action(const std::vector<std::string>& args) override;
@@ -347,6 +348,7 @@
std::optional<std::string> stable_id_file_path_;
std::vector<std::string> split_args_;
std::optional<std::string> trace_folder_;
+ std::vector<std::string> feature_flags_args_;
};
}// namespace aapt
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 6857333..d3a8b03 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -327,7 +327,8 @@
+"return$maybeCast this;"
}
- val javadocSeeSetter = "/** @see #$setterName */"
+ val javadocSeeSetter =
+ if (isHidden()) "/** @see #$setterName @hide */" else "/** @see #$setterName */"
val adderName = "add$SingularName"
val singularNameCustomizationHint = if (SingularNameOrNull == null) {
@@ -750,6 +751,15 @@
}
}
+fun FieldInfo.isHidden(): Boolean {
+ if (javadocFull != null) {
+ (javadocFull ?: "/**\n */").lines().forEach {
+ if (it.contains("@hide")) return true
+ }
+ }
+ return false
+}
+
fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
if (javadocFull != null || forceHide) {
var hidden = false