Merge "Update javadocs for OVERRIDE_MIN_ASPECT_RATIO" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index db51537..e3cbd92 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -14,12 +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}",
@@ -567,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",
@@ -599,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 7d5e788..b5f7e99 100644
--- a/Android.bp
+++ b/Android.bp
@@ -614,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",
@@ -706,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/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/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/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/core/api/current.txt b/core/api/current.txt
index 177352f..cce8329 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13322,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();
@@ -33050,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);
}
@@ -44504,7 +44507,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8ea1c65..119e0ad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2452,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();
@@ -2526,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);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a0b6fb7..83234a5 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
}
@@ -3491,10 +3493,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);
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/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/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/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/core/java/android/app/usage/ParcelableUsageEventList.aidl
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
rename 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/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 75370d9..1c917ee 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -861,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.
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index 554de0c..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;
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/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/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/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/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/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 4bb401a..a2f1ce1e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12067,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/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/HotwordTrainingDataLimitEnforcer.java b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
index 76e506c..7eb5280 100644
--- a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
+++ b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java
@@ -27,10 +27,9 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
+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
@@ -111,9 +110,9 @@
}
private boolean incrementTrainingDataEgressCountLocked() {
- SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
- dt.setTimeZone(TimeZone.getTimeZone("UTC"));
- String currentDate = dt.format(new Date());
+ 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);
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dfada58..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();
@@ -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/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/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/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a3e2706..8d11672 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1934,15 +1934,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/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/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 0a81209..8c91be8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4653,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
@@ -4717,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.
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/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/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/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/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/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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 49606f0..7743ad5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1763,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.
@@ -1783,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}
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 9e53380..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();
}
/**
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 405f0b2..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
@@ -58,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;
@@ -413,6 +414,33 @@
.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}
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 96839c5..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
@@ -1139,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)
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/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/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/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/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bb262d3..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;
@@ -255,7 +260,7 @@
mOnCaptionButtonClickListener,
mOnCaptionLongClickListener,
mAppName,
- mAppIcon
+ mAppIconBitmap
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
@@ -362,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) {
@@ -386,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);
}
@@ -455,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 044c033..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
@@ -269,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);
@@ -283,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,
@@ -348,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);
@@ -569,6 +569,7 @@
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mCaptionHeight;
int mWidth;
int mHeight;
T mRootView;
@@ -576,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/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/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 10c880d..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.
@@ -208,6 +188,19 @@
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.
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/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/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/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/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
index 02d76304..f988837 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
@@ -37,7 +37,7 @@
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
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/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/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/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/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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e6a82e8..17cc9f8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -485,6 +485,8 @@
"motion_tool_lib",
"androidx.core_core-animation-testing-nodeps",
"androidx.compose.ui_ui",
+ "flag-junit",
+ "platform-test-annotations",
],
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 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/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/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/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/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/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..4c41ca4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -629,7 +629,7 @@
<!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
<!-- QuickSettings: Bluetooth dialog subtitle [CHAR LIMIT=NONE]-->
- <string name="quick_settings_bluetooth_tile_subtitle">Tap a device to connect</string>
+ <string name="quick_settings_bluetooth_tile_subtitle">Tap to connect or disconnect a device </string>
<!-- QuickSettings: Bluetooth dialog pair new devices [CHAR LIMIT=NONE]-->
<string name="pair_new_bluetooth_devices">Pair new device</string>
<!-- QuickSettings: Bluetooth dialog see all devices [CHAR LIMIT=NONE]-->
@@ -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/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/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/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/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 8d5b84f..7bca86e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -15,18 +15,26 @@
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
@@ -34,6 +42,7 @@
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
@@ -57,4 +66,65 @@
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/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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8c81fbb..28ba5f3 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 =
@@ -123,7 +118,7 @@
// 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
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/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/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/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/viewmodel/QSTileLifecycle.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/viewmodel/QSTileLifecycle.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
index 6d7c576..c73afe8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.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.viewmodel
+package com.android.systemui.log.dagger
-enum class QSTileLifecycle {
- ALIVE,
- DEAD,
-}
+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/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 1962119..98a3896 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -16,7 +16,6 @@
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
@@ -66,6 +65,19 @@
}
/**
+ * 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.
@@ -78,47 +90,6 @@
}
}
- /**
- * Request to log that the permission request moved to the given state.
- *
- * Should not be used for the initialization state, since that should use {@link
- * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
- * sessionCreationSource.
- */
- fun notifyPermissionProgress(state: Int) {
- // TODO validate state is valid
- notifyToServer(state, SessionCreationSource.UNKNOWN)
- }
-
- /**
- * Notifies system server that we are handling a particular state during the consent flow.
- *
- * Only used for emitting atoms.
- *
- * @param state The state that SystemUI is handling during the consent flow. Must be a valid
- * state defined in the MediaProjectionState enum.
- * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
- * Indicates the entry point for requesting the permission. Must be a valid state defined in
- * the SessionCreationSource enum.
- */
- private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
- Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
- try {
- service.notifyPermissionRequestStateChange(
- Process.myUid(),
- state,
- sessionCreationSource.toMetricsConstant()
- )
- } catch (e: RemoteException) {
- Log.e(
- TAG,
- "Error notifying server of permission flow state $state from source " +
- "$sessionCreationSource",
- e
- )
- }
- }
-
companion object {
const val TAG = "MediaProjectionMetricsLogger"
}
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 04d5566..50e9e751 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -210,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()
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 67ef119..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
@@ -76,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 f7cc589..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)
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/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/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 a3e3850..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,6 +16,7 @@
package com.android.systemui.qs.tiles.base.interactor
+import android.os.UserHandle
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -29,13 +30,12 @@
/**
* Returns a data flow scoped to the user. This means the subscription will live when the tile
- * is listened for the [userId]. It's cancelled when the tile is not listened or the user
- * changes.
+ * is listened for the [user]. It's cancelled when the tile is not listened or the user changes.
*
* You can use [Flow.onStart] on the returned to update the tile with the current state as soon
* as possible.
*/
- fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
+ fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
/**
* Returns tile availability - whether this device currently supports this tile.
@@ -43,5 +43,5 @@
* You can use [Flow.onStart] on the returned to update the tile with the current state as soon
* as possible.
*/
- fun availability(userId: Int): Flow<Boolean>
+ fun availability(user: UserHandle): Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
index 102fa36..77ff609 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
@@ -16,11 +16,12 @@
package com.android.systemui.qs.tiles.base.interactor
+import android.os.UserHandle
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
/** @see QSTileUserActionInteractor.handleInput */
data class QSTileInput<T>(
- val userId: Int,
+ val user: UserHandle,
val action: QSTileUserAction,
val data: T,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
deleted file mode 100644
index 14de5eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ /dev/null
@@ -1,308 +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.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.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.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.throttle
-import com.android.systemui.util.time.SystemClock
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.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 it's moved to
- * [QSTileLifecycle.ALIVE] state.
- *
- * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class BaseQSTileViewModel<DATA_TYPE>
-@VisibleForTesting
-constructor(
- override val config: QSTileConfig,
- private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
- private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
- private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
- 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,
-) : QSTileViewModel {
-
- @AssistedInject
- constructor(
- @Assisted config: QSTileConfig,
- @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
- @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
- @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
- disabledByPolicyInteractor: DisabledByPolicyInteractor,
- userRepository: UserRepository,
- falsingManager: FalsingManager,
- qsTileAnalytics: QSTileAnalytics,
- qsTileLogger: QSTileLogger,
- systemClock: SystemClock,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ) : this(
- config,
- userActionInteractor,
- tileDataInteractor,
- mapper,
- disabledByPolicyInteractor,
- userRepository,
- falsingManager,
- qsTileAnalytics,
- qsTileLogger,
- systemClock,
- backgroundDispatcher,
- CoroutineScope(SupervisorJob())
- )
-
- private val userIds: MutableStateFlow<Int> =
- MutableStateFlow(userRepository.getSelectedUserInfo().id)
- private val 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 lateinit var tileData: SharedFlow<DATA_TYPE>
-
- override lateinit var state: SharedFlow<QSTileState>
- override val isAvailable: StateFlow<Boolean> =
- userIds
- .flatMapLatest { tileDataInteractor.availability(it) }
- .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
- .map { data ->
- mapper.map(config, data).also { state ->
- qsTileLogger.logStateUpdate(spec, state, data)
- }
- }
- .flowOn(backgroundDispatcher)
- .shareIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- replay = 1,
- )
- }
- QSTileLifecycle.DEAD -> {
- Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
- tileScope.coroutineContext.cancelChildren()
- }
- }
- currentLifeState = lifecycle
- }
-
- private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
- userIds
- .flatMapLatest { userId ->
- val updateTriggers =
- merge(
- userInputFlow(userId),
- forceUpdates
- .map { DataUpdateTrigger.ForceUpdate }
- .onEach { qsTileLogger.logForceUpdate(spec) },
- )
- .onStart {
- emit(DataUpdateTrigger.InitialRequest)
- qsTileLogger.logInitialRequest(spec)
- }
- tileDataInteractor
- .tileData(userId, 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(userId: Int): Flow<DataUpdateTrigger> {
- return userInputs
- .filterFalseActions()
- .filterByPolicy(userId)
- .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(userId, action, data))
- }
- .onEach { userActionInteractor.handleInput(it.input) }
- .flowOn(backgroundDispatcher)
- }
-
- private fun Flow<QSTileUserAction>.filterByPolicy(userId: Int): Flow<QSTileUserAction> =
- when (config.policy) {
- is QSTilePolicy.NoRestrictions -> this
- is QSTilePolicy.Restricted ->
- filter { action ->
- val result =
- disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
- !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
- if (isDisabled) {
- qsTileLogger.logUserActionRejectedByPolicy(action, spec)
- }
- }
- }
- }
-
- private fun Flow<QSTileUserAction>.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
- }
-
- /**
- * Factory interface for assisted inject. Dagger has bad time supporting generics in assisted
- * injection factories now. That's why you need to create an interface implementing this one and
- * annotate it with [dagger.assisted.AssistedFactory].
- *
- * ex: @AssistedFactory interface FooFactory : BaseQSTileViewModel.Factory<FooData>
- */
- interface Factory<T> {
-
- /**
- * @param config contains all the static information (like TileSpec) about the tile.
- * @param userActionInteractor encapsulates user input processing logic. Use it to start
- * activities, show dialogs or otherwise update the tile state.
- * @param tileDataInteractor provides [DATA_TYPE] and its availability.
- * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
- * layer. It's called in [backgroundDispatcher], so it's safe to perform long running
- * operations there.
- */
- fun create(
- config: QSTileConfig,
- userActionInteractor: QSTileUserActionInteractor<T>,
- tileDataInteractor: QSTileDataInteractor<T>,
- mapper: QSTileDataToStateMapper<T>,
- ): BaseQSTileViewModel<T>
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/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..4096226 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) {
@@ -102,9 +105,11 @@
showSeeAll: Boolean,
showPairNewDevice: Boolean
) {
+ val start = systemClock.elapsedRealtime()
deviceItemAdapter.refreshDeviceItemList(deviceItem) {
seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+ logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start)
}
}
@@ -117,6 +122,7 @@
toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { _, isChecked ->
mutableBluetoothStateToggle.value = isChecked
+ logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString())
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
new file mode 100644
index 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/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
similarity index 60%
rename from core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
index 7a0f438..4a4ba2b 100644
--- a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package android.hardware.biometrics;
+package com.android.systemui.qs.tiles.impl.custom.di.bound
+
+import javax.inject.Scope
/**
- * Communication channel to propagate biometric prompt status. Implementation of this interface
- * should be registered in BiometricService#registerBiometricPromptStatusListener.
- * @hide
+ * 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].
*/
-oneway interface IBiometricPromptStatusListener {
- void onBiometricPromptShowing();
- void onBiometricPromptIdle();
-}
\ No newline at end of file
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class CustomTileBoundScope
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.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/viewmodel/QSTileLifecycle.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
index 6d7c576..efc7431 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.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.viewmodel
+package com.android.systemui.qs.tiles.impl.custom.di.bound
-enum class QSTileLifecycle {
- ALIVE,
- DEAD,
-}
+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/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 33f55ab..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,6 +17,7 @@
package com.android.systemui.qs.tiles.viewmodel
import android.content.Context
+import android.os.UserHandle
import android.util.Log
import android.view.View
import androidx.annotation.GuardedBy
@@ -134,7 +135,7 @@
qsTileViewModel.currentState?.supportedActions?.contains(action) == true
override fun userSwitch(currentUser: Int) {
- qsTileViewModel.onUserIdChanged(currentUser)
+ qsTileViewModel.onUserChanged(UserHandle.of(currentUser))
}
@Deprecated(
@@ -180,7 +181,7 @@
override fun destroy() {
stateJob?.cancel()
availabilityJob?.cancel()
- qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+ qsTileViewModel.destroy()
}
override fun getState(): QSTile.State? =
@@ -188,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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index ea1205a..05f125f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -163,8 +163,7 @@
}
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(
@@ -174,7 +173,8 @@
/* controller= */ this,
activityStarter,
mUserContextProvider,
- onStartRecordingClicked)
+ onStartRecordingClicked,
+ mMediaProjectionMetricsLogger)
: new ScreenRecordDialog(
context,
/* controller= */ this,
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 3b3aa53..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
@@ -50,12 +51,15 @@
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
) {
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/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 7d81e55..c8669072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -48,6 +48,13 @@
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,
@Nullable final Runnable endRunnable) {
view.animate().cancel();
@@ -155,6 +162,13 @@
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();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 2e1e395..49743bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,14 +29,13 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.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;
@@ -62,7 +61,6 @@
private static final long MAX_RANKING_DELAY_MILLIS = 500L;
private final Context mContext;
- private final FeatureFlagsClassic mFeatureFlags;
private final NotificationManager mNotificationManager;
private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor;
private final SystemClock mSystemClock;
@@ -80,7 +78,6 @@
@Inject
public NotificationListener(
Context context,
- FeatureFlagsClassic featureFlags,
NotificationManager notificationManager,
SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor,
SystemClock systemClock,
@@ -88,7 +85,6 @@
PluginManager pluginManager) {
super(pluginManager);
mContext = context;
- mFeatureFlags = featureFlags;
mNotificationManager = notificationManager;
mStatusIconInteractor = statusIconInteractor;
mSystemClock = systemClock;
@@ -106,6 +102,7 @@
/** Registers a listener that's notified when any notification-related settings change. */
@Deprecated
public void addNotificationSettingsListener(NotificationSettingsListener listener) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
mSettingsListeners.add(listener);
}
@@ -240,7 +237,7 @@
@Override
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
} else {
for (NotificationSettingsListener listener : mSettingsListeners) {
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/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 07e84bb..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
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -25,6 +23,7 @@
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
@@ -38,7 +37,6 @@
class StackCoordinator
@Inject
internal constructor(
- private val featureFlags: FeatureFlagsClassic,
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
private val notificationIconAreaController: NotificationIconAreaController,
private val renderListInteractor: RenderNotificationListInteractor,
@@ -52,7 +50,7 @@
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
controller.setNotifStats(calculateNotifStats(entries))
- if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled) {
renderListInteractor.setRenderedList(entries)
} else {
notificationIconAreaController.updateNotificationIcons(entries)
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 246933a..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
@@ -18,24 +18,15 @@
import android.content.Context
import android.graphics.Rect
import android.view.View
-import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.ListEntry
-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.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.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
/**
* Controller class for [NotificationIconContainer]. This implementation serves as a temporary
@@ -45,67 +36,16 @@
* can be used directly.
*/
@SysUISingleton
-class NotificationIconAreaControllerViewBinderWrapperImpl
-@Inject
-constructor(
- private val configuration: ConfigurationState,
- private val configurationController: ConfigurationController,
- private val dozeParameters: DozeParameters,
- private val featureFlags: FeatureFlagsClassic,
- private val screenOffAnimationController: ScreenOffAnimationController,
- private val shelfIconViewStore: ShelfNotificationIconViewStore,
- private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
- private val aodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
- private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) : NotificationIconAreaController {
-
- private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
-
- private var shelfIcons: NotificationIconContainer? = null
- private var aodIcons: NotificationIconContainer? = null
- private var aodBindJob: DisposableHandle? = null
+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,
- configuration,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- aodIconViewStore,
- )
- }
+ override fun setupAodIcons(aodIcons: NotificationIconContainer?) = unsupported
override fun setupShelf(notificationShelfController: NotificationShelfController) =
NotificationShelfViewBinderWrapperControllerImpl.unsupported
- override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.isUnexpectedlyInLegacyMode()) {
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configuration,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- shelfIconViewStore,
- )
- shelfIcons = icons
- }
- }
+ override fun setShelfIcons(icons: NotificationIconContainer) = unsupported
override fun onDensityOrFontScaleChanged(context: Context) = unsupported
@@ -126,15 +66,14 @@
override fun onThemeChanged() = unsupported
- override fun getHeight(): Int {
- return if (aodIcons == null) 0 else aodIcons!!.height
- }
+ override fun getHeight(): Int = unsupported
companion object {
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 7592619..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
@@ -17,13 +17,15 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.graphics.Color
import android.graphics.Rect
import android.view.View
+import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.FrameLayout
+import androidx.annotation.ColorInt
import androidx.collection.ArrayMap
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
+import androidx.lifecycle.lifecycleScope
import com.android.app.animation.Interpolators
import com.android.internal.policy.SystemBarUtils
import com.android.internal.util.ContrastColorUtil
@@ -37,9 +39,12 @@
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.NotificationIconContainerViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
+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
@@ -49,6 +54,7 @@
import com.android.systemui.util.kotlin.mapValuesNotNullTo
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.stateFlow
+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
@@ -62,12 +68,53 @@
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,
@@ -75,60 +122,72 @@
screenOffAnimationController: ScreenOffAnimationController,
viewStore: IconViewStore,
): DisposableHandle {
- val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
return view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { bindAnimationsEnabled(viewModel, view) }
- launch { bindIsDozing(viewModel, view, dozeParameters) }
- // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
- // view-binder
+ lifecycleScope.launch {
launch {
- bindVisibility(
- viewModel,
- view,
- configuration,
- featureFlags,
- screenOffAnimationController,
- )
- }
- launch { bindIconColors(viewModel, view, contrastColorUtil) }
- launch {
- bindIconViewData(
- viewModel,
+ viewModel.icons.bindIcons(
view,
configuration,
configurationController,
viewStore,
)
}
- launch { bindIsolatedIcon(viewModel, view, viewStore) }
+ 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 {
+ viewModel.isVisible.bindIsVisible(
+ view,
+ configuration,
+ featureFlags,
+ screenOffAnimationController,
+ )
+ }
+ launch {
+ configuration
+ .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
+ .bindIconColors(view)
+ }
}
}
}
- private suspend fun bindAnimationsEnabled(
- viewModel: NotificationIconContainerViewModel,
- view: NotificationIconContainer
- ) {
- viewModel.animationsEnabled.collect(view::setAnimationsEnabled)
+ /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
+ private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
+ collect(view::setAnimationsEnabled)
}
- private suspend fun bindIconColors(
- viewModel: NotificationIconContainerViewModel,
+ /**
+ * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
+ * of the [children] of an [NotificationIconContainer].
+ */
+ private suspend fun Flow<NotificationIconColorLookup>.bindIconColors(
view: NotificationIconContainer,
contrastColorUtil: ContrastColorUtil,
) {
- viewModel.iconColors
- .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
- .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) }
+ mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+ .collect { iconLookup -> view.applyTint(iconLookup, contrastColorUtil) }
}
- private suspend fun bindIsDozing(
- viewModel: NotificationIconContainerViewModel,
+ /**
+ * 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,
) {
- viewModel.isDozing.collect { isDozing ->
+ collect { isDozing ->
if (isDozing.isAnimating) {
val animate = !dozeParameters.displayNeedsBlanking
view.setDozing(
@@ -147,19 +206,18 @@
}
}
- private suspend fun bindIsolatedIcon(
- viewModel: NotificationIconContainerViewModel,
+ private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
view: NotificationIconContainer,
viewStore: IconViewStore,
) {
coroutineScope {
launch {
- viewModel.isolatedIconLocation.collect { location ->
+ isolatedIconLocation.collect { location ->
view.setIsolatedIconLocation(location, true)
}
}
launch {
- viewModel.isolatedIcon.collect { iconInfo ->
+ isolatedIcon.collect { iconInfo ->
val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
if (iconInfo.isAnimating) {
view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
@@ -171,8 +229,8 @@
}
}
- private suspend fun bindIconViewData(
- viewModel: NotificationIconContainerViewModel,
+ /** Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. */
+ private suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
configuration: ConfigurationState,
configurationController: ConfigurationController,
@@ -205,11 +263,11 @@
}
}
- var prevIcons = IconsViewData()
- viewModel.iconsViewData.sample(layoutParams, ::Pair).collect {
- (iconsData: IconsViewData, layoutParams: FrameLayout.LayoutParams),
+ var prevIcons = NotificationIconsViewData()
+ sample(layoutParams, ::Pair).collect {
+ (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams),
->
- val iconsDiff = IconsViewData.computeDifference(iconsData, prevIcons)
+ val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
prevIcons = iconsData
val replacingIcons =
@@ -255,30 +313,27 @@
// TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
// can be moved there and cleaned up.
- private fun applyTint(
- view: NotificationIconContainer,
- iconColors: IconColors,
+ private fun ViewGroup.applyTint(
+ iconColors: NotificationIconColors,
contrastColorUtil: ContrastColorUtil,
) {
- view.children
+ children
.filterIsInstance<StatusBarIconView>()
.filter { it.width != 0 }
- .forEach { iv -> updateTintForIcon(iv, iconColors, contrastColorUtil) }
+ .forEach { iv -> iv.updateTintForIcon(iconColors, contrastColorUtil) }
}
- private fun updateTintForIcon(
- v: StatusBarIconView,
- iconColors: IconColors,
+ private fun StatusBarIconView.updateTintForIcon(
+ iconColors: NotificationIconColors,
contrastColorUtil: ContrastColorUtil,
) {
- val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
- val isColorized = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
- v.staticDrawableColor = iconColors.staticDrawableColor(v.viewBounds, isColorized)
- v.setDecorColor(iconColors.tint)
+ 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 bindVisibility(
- viewModel: NotificationIconContainerViewModel,
+ private suspend fun Flow<AnimatedValue<Boolean>>.bindIsVisible(
view: NotificationIconContainer,
configuration: ConfigurationState,
featureFlags: FeatureFlagsClassic,
@@ -287,7 +342,7 @@
val iconAppearTranslation =
configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
- viewModel.isVisible.collect { isVisible ->
+ collect { isVisible ->
view.animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
@@ -304,7 +359,7 @@
view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
- animateInIconTranslation(view, statusViewMigrated)
+ view.animateInIconTranslation(statusViewMigrated)
if (isVisible.value) {
CrossFadeHelper.fadeIn(view, animatorListener)
} else {
@@ -313,15 +368,14 @@
}
!isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
- animateInIconTranslation(view, statusViewMigrated)
+ 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,
@@ -330,7 +384,7 @@
}
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, animatorListener)
}
@@ -338,8 +392,7 @@
}
}
- private fun appearIcons(
- view: View,
+ private fun View.appearIcons(
animate: Boolean,
iconAppearTranslation: Int,
statusViewMigrated: Boolean,
@@ -347,11 +400,10 @@
) {
if (animate) {
if (!statusViewMigrated) {
- view.translationY = -iconAppearTranslation.toFloat()
+ translationY = -iconAppearTranslation.toFloat()
}
- view.alpha = 0f
- view
- .animate()
+ alpha = 0f
+ animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
@@ -359,40 +411,29 @@
.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().animateInIconTranslation().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)
- private const val AOD_ICONS_APPEAR_DURATION: Long = 200
-
- 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,
- )
- }
-
/** 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
}
/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
@@ -424,3 +465,15 @@
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 120d342..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,10 +15,7 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-import android.graphics.Color
import android.graphics.Rect
-import androidx.annotation.ColorInt
-import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FeatureFlagsClassic
@@ -27,14 +24,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.res.R
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.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.pairwise
@@ -47,8 +39,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed on the always-on display. */
@@ -56,7 +46,6 @@
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
- configuration: ConfigurationState,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val featureFlags: FeatureFlagsClassic,
@@ -66,14 +55,10 @@
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
-) : NotificationIconContainerViewModel {
+) {
- override val iconColors: Flow<ColorLookup> =
- configuration.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR).map { tint ->
- ColorLookup { IconColorsImpl(tint) }
- }
-
- override val animationsEnabled: Flow<Boolean> =
+ /** Are changes to the icon container animated? */
+ val animationsEnabled: Flow<Boolean> =
combine(
shadeInteractor.isShadeTouchable,
keyguardInteractor.isKeyguardVisible,
@@ -81,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 ->
@@ -98,7 +84,8 @@
.distinctUntilChanged()
.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,
@@ -136,17 +123,14 @@
}
.distinctUntilChanged()
- override val iconsViewData: Flow<IconsViewData> =
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
iconsInteractor.aodNotifs.map { entries ->
- IconsViewData(
+ NotificationIconsViewData(
visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
)
}
- override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
- flowOf(AnimatedValue.NotAnimating(null))
- override val isolatedIconLocation: Flow<Rect> = emptyFlow()
-
/** Is there an expanded pulse, are we animating in response? */
private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
return notificationsKeyguardInteractor.isPulseExpanding
@@ -182,11 +166,7 @@
.toAnimatedValueFlow()
}
- private class IconColorsImpl(override val tint: Int) : IconColors {
+ private class IconColorsImpl(override val tint: Int) : NotificationIconColors {
override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
}
-
- companion object {
- @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
- }
}
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 c6aabb7..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,16 +15,9 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-import android.graphics.Rect
import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
-import com.android.systemui.util.ui.AnimatedValue
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. */
@@ -32,19 +25,11 @@
@Inject
constructor(
interactor: NotificationIconsInteractor,
-) : NotificationIconContainerViewModel {
-
- override val animationsEnabled: Flow<Boolean> = flowOf(true)
- override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override val iconColors: Flow<ColorLookup> = emptyFlow()
- override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
- flowOf(AnimatedValue.NotAnimating(null))
- override val isolatedIconLocation: Flow<Rect> = emptyFlow()
-
- override val iconsViewData: Flow<IconsViewData> =
+) {
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
interactor.filteredNotifSet().map { entries ->
- IconsViewData(
+ 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 4d14024..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
@@ -22,10 +22,6 @@
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.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
@@ -35,7 +31,6 @@
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
@@ -49,8 +44,10 @@
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,
@@ -58,14 +55,15 @@
panelTouchesEnabled && !isKeyguardShowing
}
- override val iconColors: Flow<ColorLookup> =
+ /** 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, _ ->
- ColorLookup { viewBounds: Rect ->
+ NotificationIconColorLookup { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
IconColorsImpl(tint, areas)
} else {
@@ -74,20 +72,19 @@
}
}
- override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
-
- override val iconsViewData: Flow<IconsViewData> =
+ /** [NotificationIconsViewData] indicating which icons to display in the view. */
+ val icons: Flow<NotificationIconsViewData> =
iconsInteractor.statusBarNotifs.map { entries ->
- IconsViewData(
+ NotificationIconsViewData(
visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
)
}
- override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ /** An Icon to show "isolated" in the IconContainer. */
+ val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
headsUpIconInteractor.isolatedNotification
.pairwise(initialValue = null)
- .sample(combine(iconsViewData, shadeInteractor.shadeExpansion, ::Pair)) {
+ .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) {
(prev, isolatedNotif),
(iconsViewData, shadeExpansion),
->
@@ -105,13 +102,14 @@
}
.toAnimatedValueFlow()
- override val isolatedIconLocation: Flow<Rect> =
+ /** 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>,
- ) : IconColors {
+ ) : NotificationIconColors {
override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
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 a611323..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ /dev/null
@@ -1,196 +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 android.graphics.Rect
-import android.graphics.drawable.Icon
-import androidx.collection.ArrayMap
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
-import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
-import com.android.systemui.util.kotlin.mapValuesNotNullTo
-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>>
-
- /** The colors with which to display the notification icons. */
- val iconColors: Flow<ColorLookup>
-
- /** [IconsViewData] indicating which icons to display in the view. */
- val iconsViewData: Flow<IconsViewData>
-
- /** An Icon to show "isolated" in the IconContainer. */
- val isolatedIcon: Flow<AnimatedValue<IconInfo?>>
-
- /** Location to show an isolated icon, if there is one. */
- val isolatedIconLocation: Flow<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 ColorLookup {
- fun iconColors(viewBounds: Rect): IconColors?
- }
-
- /** Colors to apply to notification icons. */
- interface IconColors {
-
- /** 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
- }
-
- /** Encapsulates the collection of notification icons present on the device. */
- data class IconsViewData(
- /** Icons that are visible in the container. */
- val visibleKeys: List<IconInfo> = 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 [IconsViewData]s. */
- data class Diff(
- /** Icons added in the newer dataset. */
- val added: List<IconInfo> = 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, IconInfo> = emptyMap(),
- )
-
- companion object {
- /**
- * Returns an [IconsViewData.Diff] calculated from a [new] and [previous][prev]
- * [IconsViewData] state.
- */
- fun computeDifference(new: IconsViewData, prev: IconsViewData): Diff {
- val added: List<IconInfo> =
- new.visibleKeys.filter {
- it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
- }
- val removed: List<IconInfo> =
- 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, IconInfo> =
- 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 IconInfo(
- val sourceIcon: Icon,
- val notifKey: String,
- val groupKey: String,
- )
-}
-
-/**
- * Construct an [IconInfo] out of an [ActiveNotificationModel], or return `null` if one cannot be
- * created due to missing information.
- */
-fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): IconInfo? {
- return sourceIcon?.let {
- groupKey?.let { groupKey ->
- IconInfo(
- sourceIcon = sourceIcon,
- notifKey = key,
- groupKey = groupKey,
- )
- }
- }
-}
-
-private val IconInfo.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/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/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/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 79448b4..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,6 +59,7 @@
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;
@@ -107,6 +108,7 @@
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;
@@ -117,10 +119,12 @@
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
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;
@@ -212,6 +216,8 @@
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final ConfigurationState mConfigurationState;
+ private final ShelfNotificationIconViewStore mShelfIconViewStore;
private View mLongPressedView;
@@ -674,7 +680,9 @@
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ ConfigurationState configurationState,
+ ShelfNotificationIconViewStore shelfIconViewStore) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mStackStateLogger = stackLogger;
@@ -724,6 +732,8 @@
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mConfigurationState = configurationState;
+ mShelfIconViewStore = shelfIconViewStore;
mView.passSplitShadeStateController(splitShadeStateController);
updateResources();
setUpView();
@@ -832,8 +842,10 @@
mViewModel.ifPresent(
vm -> NotificationListViewBinder
- .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController,
- mConfigurationController));
+ .bind(mView, vm, mConfigurationState, mConfigurationController,
+ mFalsingManager,
+ mNotifIconAreaController,
+ mShelfIconViewStore));
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
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 a3792cf..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,23 +17,21 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
-import com.android.systemui.flags.FeatureFlagsClassic
+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.statusbar.policy.onDensityOrFontScaleChanged
-import com.android.systemui.statusbar.policy.onThemeChanged
import com.android.systemui.util.traceSection
-import com.android.systemui.util.view.reinflateAndBindLatest
-import kotlinx.coroutines.flow.merge
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
@@ -41,10 +39,11 @@
fun bind(
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
- falsingManager: FalsingManager,
- featureFlags: FeatureFlagsClassic,
- iconAreaController: NotificationIconAreaController,
+ configuration: ConfigurationState,
configurationController: ConfigurationController,
+ falsingManager: FalsingManager,
+ iconAreaController: NotificationIconAreaController,
+ shelfIconViewStore: ShelfNotificationIconViewStore,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -52,28 +51,24 @@
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 {
- LayoutInflater.from(view.context).reinflateAndBindLatest(
+ configuration.reinflateAndBindLatest(
R.layout.status_bar_notification_footer,
view,
attachToRoot = false,
- // TODO(b/305930747): This may lead to duplicate invocations if both flows emit,
- // find a solution to only emit one event.
- merge(
- configurationController.onThemeChanged,
- configurationController.onDensityOrFontScaleChanged,
- ),
- ) { view ->
+ ) { footerView: FooterView ->
traceSection("bind FooterView") {
- FooterViewBinder.bind(view as FooterView, footerViewModel)
+ FooterViewBinder.bind(footerView, footerViewModel)
}
}
}
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 8295f65..897bb42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -206,6 +206,7 @@
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;
@@ -236,8 +237,6 @@
import dalvik.annotation.optimization.NeverCompile;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
@@ -249,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.
@@ -809,8 +810,6 @@
mShadeExpansionStateManager.addExpansionListener(shadeExpansionListener);
shadeExpansionListener.onPanelExpansionChanged(currentState);
- mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
-
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mActivityLaunchAnimator = activityLaunchAnimator;
@@ -1397,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() {
@@ -2635,7 +2620,7 @@
!mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
mShadeSurface.setTouchAndAnimationDisabled(disabled);
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (!NotificationIconContainerRefactor.isEnabled()) {
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
}
@@ -3102,7 +3087,7 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (!NotificationIconContainerRefactor.isEnabled()) {
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
}
}
@@ -3122,7 +3107,7 @@
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (!NotificationIconContainerRefactor.isEnabled()) {
mNotificationIconAreaController.onThemeChanged();
}
}
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 66341ba..600d4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -38,7 +38,6 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -49,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;
/**
@@ -178,7 +177,7 @@
void fireNotificationPulse(NotificationEntry entry) {
Runnable pulseSuppressedListener = () -> {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mHeadsUpManager.removeNotification(
entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
} else {
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 8fee5c0..beeee1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -27,7 +27,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -42,6 +41,7 @@
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;
@@ -179,7 +179,7 @@
mHeadsUpManager.addListener(this);
mView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
updateIsolatedIconLocation(true);
}
mWakeUpCoordinator.addListener(this);
@@ -197,7 +197,7 @@
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
}
mWakeUpCoordinator.removeListener(this);
@@ -208,7 +208,7 @@
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mHeadsUpNotificationIconInteractor
.setIsolatedIconLocation(mView.getIconDrawingRect());
} else {
@@ -250,7 +250,7 @@
setShown(true);
animateIsolation = !isExpanded();
}
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
newEntry == null ? null : newEntry.getKey());
} else {
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/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 4284c96c..dd32434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -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 535f6ac..efb8e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -40,10 +40,9 @@
import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.settingslib.Utils;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
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;
@@ -133,9 +132,6 @@
}
}.setDuration(CONTENT_FADE_DURATION);
- private final RefactorFlag mIconContainerRefactorFlag =
- RefactorFlag.forView(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
-
/* Maximum number of icons on AOD when also showing overflow dot. */
private int mMaxIconsOnAod;
@@ -352,7 +348,7 @@
StatusBarIconView iconView = (StatusBarIconView) child;
Icon sourceIcon = iconView.getSourceIcon();
String groupKey = iconView.getNotification().getGroupKey();
- if (mIconContainerRefactorFlag.isEnabled()) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
if (mReplacingIcons == null) {
return false;
}
@@ -695,18 +691,18 @@
}
public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
- mIconContainerRefactorFlag.assertInLegacyMode();
+ NotificationIconContainerRefactor.assertInLegacyMode();
mReplacingIconsLegacy = replacingIcons;
}
public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
- if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mReplacingIcons = replacingIcons;
}
@Deprecated
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
- mIconContainerRefactorFlag.assertInLegacyMode();
+ NotificationIconContainerRefactor.assertInLegacyMode();
if (animated) {
showIconIsolatedAnimated(icon, null);
} else {
@@ -716,14 +712,14 @@
public void showIconIsolatedAnimated(StatusBarIconView icon,
@Nullable Runnable onAnimationEnd) {
- if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
mIsolatedIconAnimationEndRunnable = onAnimationEnd;
showIconIsolated(icon);
}
public void showIconIsolated(StatusBarIconView icon) {
- if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mIsolatedIcon = icon;
updateState();
}
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/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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index e2a4714..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
@@ -44,7 +44,6 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -59,12 +58,10 @@
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.icon.ui.viewmodel.NotificationIconContainerViewModel;
-import com.android.systemui.statusbar.phone.DozeParameters;
+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.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
@@ -86,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;
@@ -96,8 +95,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -155,12 +152,10 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final NotificationIconContainerViewModel mStatusBarIconsViewModel;
+ private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
private final ConfigurationState mConfigurationState;
private final ConfigurationController mConfigurationController;
- private final DozeParameters mDozeParameters;
- private final ScreenOffAnimationController mScreenOffAnimationController;
- private final NotificationIconContainerViewBinder.IconViewStore mStatusBarIconViewStore;
+ private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -252,8 +247,6 @@
NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
ConfigurationState configurationState,
ConfigurationController configurationController,
- DozeParameters dozeParameters,
- ScreenOffAnimationController screenOffAnimationController,
StatusBarNotificationIconViewStore statusBarIconViewStore,
DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
@@ -283,8 +276,6 @@
mStatusBarIconsViewModel = statusBarIconsViewModel;
mConfigurationState = configurationState;
mConfigurationController = configurationController;
- mDozeParameters = dozeParameters;
- mScreenOffAnimationController = screenOffAnimationController;
mStatusBarIconViewStore = statusBarIconViewStore;
mDemoModeController = demoModeController;
}
@@ -317,7 +308,7 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mDemoModeController.addCallback(mDemoModeCallback);
}
}
@@ -326,7 +317,7 @@
public void onDestroy() {
super.onDestroy();
mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mDemoModeController.removeCallback(mDemoModeCallback);
}
}
@@ -469,7 +460,7 @@
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
mNotificationIconAreaInner =
LayoutInflater.from(getContext())
.inflate(R.layout.notification_icon_area, notificationIconArea, true);
@@ -480,9 +471,6 @@
mStatusBarIconsViewModel,
mConfigurationState,
mConfigurationController,
- mDozeParameters,
- mFeatureFlags,
- mScreenOffAnimationController,
mStatusBarIconViewStore);
} else {
mNotificationIconAreaInner =
@@ -606,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/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/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/util/view/LayoutInflaterExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt
deleted file mode 100644
index 6d45d23..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt
+++ /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.systemui.util.view
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Perform an inflation right away, then re-inflate whenever the [flow] emits, and call [onInflate]
- * on the resulting view each time. Dispose 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 {
- * LayoutInflater.from(parentView.context)
- * .reinflateOnChange(
- * R.layout.my_layout,
- * parentView,
- * attachToRoot = false,
- * coroutineScope = lifecycleScope,
- * configurationController.onThemeChanged,
- * ),
- * ) { view ->
- * ChildViewBinder.bind(view as ChildView, childViewModel)
- * }
- * }
- * ```
- *
- * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
- * [DisposableHandle].
- */
-suspend fun LayoutInflater.reinflateAndBindLatest(
- resource: Int,
- root: ViewGroup?,
- attachToRoot: Boolean,
- flow: Flow<Unit>,
- onInflate: (View) -> DisposableHandle?,
-) = coroutineScope {
- val viewFlow: Flow<View> = stateFlow(flow) { inflate(resource, root, attachToRoot) }
- viewFlow.bindLatest(onInflate)
-}
-
-/**
- * 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 Flow<View>.bindLatest(bind: (View) -> DisposableHandle?) {
- this.collectLatest { view ->
- val disposableHandle = bind(view)
- disposableHandle?.awaitCancellationThenDispose()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index f49ba64..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,6 +38,7 @@
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
@@ -75,6 +77,9 @@
@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(),
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/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 f15164e..284c273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -35,7 +35,6 @@
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -68,7 +67,6 @@
@LargeTest
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
@@ -135,7 +133,9 @@
@After
public void tearDown() throws Exception {
- mController.deleteWindowMagnification();
+ mInstrumentation.runOnMainSync(() -> {
+ mController.deleteWindowMagnification();
+ });
}
@Test
@@ -170,8 +170,7 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@@ -179,10 +178,11 @@
@Test
public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback()
throws RemoteException {
- mWindowMagnificationAnimationController.enableWindowMagnification(1,
+ 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);
}
@@ -196,13 +196,15 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- advanceTimeBy(mWaitAnimationDuration);
+ resetMockObjects();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -224,9 +226,8 @@
enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
Float.NaN, Float.NaN, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
// The callback in 2nd enableWindowMagnification will return true
verify(mAnimationCallback2).onResult(true);
@@ -245,12 +246,14 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -279,12 +282,14 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -314,8 +319,7 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
assertEquals(WindowMagnificationAnimationController.STATE_DISABLED,
@@ -333,8 +337,7 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
@@ -347,9 +350,8 @@
mAnimationCallback);
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
+ enableWindowMagnificationAndWaitAnimating(
+ mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -368,20 +370,25 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ 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);
- // 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();
+
+ 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(),
@@ -410,8 +417,7 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
+ enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
@@ -425,9 +431,8 @@
mAnimationCallback);
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
Float.NaN, Float.NaN, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -445,12 +450,14 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -471,25 +478,26 @@
final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- windowBounds.exactCenterX(), windowBounds.exactCenterY(),
- offsetRatio, offsetRatio, mAnimationCallback);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ windowBounds.exactCenterX(), windowBounds.exactCenterY(),
+ offsetRatio, offsetRatio, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
- // We delay the time of verifying to wait for the measurement and layout of the view
- mHandler.postDelayed(() -> {
- final View attachedView = mWindowManager.getAttachedView();
- assertNotNull(attachedView);
- final Rect mirrorViewBound = new Rect();
- final View mirrorView = attachedView.findViewById(R.id.surface_view);
- assertNotNull(mirrorView);
- mirrorView.getBoundsOnScreen(mirrorViewBound);
+ // Wait for Rects update
+ waitForIdleSync();
+ final View attachedView = mWindowManager.getAttachedView();
+ assertNotNull(attachedView);
+ final Rect mirrorViewBound = new Rect();
+ final View mirrorView = attachedView.findViewById(R.id.surface_view);
+ assertNotNull(mirrorView);
+ mirrorView.getBoundsOnScreen(mirrorViewBound);
- assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
- (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
- assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
- (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
- }, 100);
+ assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
+ (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
+ assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
+ (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
}
@Test
@@ -498,9 +506,11 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, mAnimationCallback);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verify(mAnimationCallback).onResult(true);
verify(mAnimationCallback, never()).onResult(false);
@@ -512,15 +522,17 @@
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
// only the last one callback will return true
verify(mAnimationCallback2).onResult(true);
@@ -538,9 +550,11 @@
enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
// The callback in moveWindowMagnifierToPosition will return true
verify(mAnimationCallback2).onResult(true);
@@ -556,9 +570,11 @@
enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- Float.NaN, Float.NaN, mAnimationCallback2);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
// The callback in moveWindowMagnifierToPosition will return true
verify(mAnimationCallback2).onResult(true);
@@ -584,6 +600,7 @@
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
+ resetMockObjects();
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
@@ -625,16 +642,18 @@
mAnimationCallback);
Mockito.reset(mSpyController);
- 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();
+ 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(),
@@ -661,7 +680,7 @@
mAnimationCallback);
Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(null);
+ deleteWindowMagnificationWithoutAnimation();
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
verify(mAnimationCallback).onResult(false);
@@ -673,6 +692,7 @@
deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
+ resetMockObjects();
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
verify(mSpyController).enableWindowMagnificationInternal(
@@ -710,8 +730,7 @@
final float offsetY =
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
+ 1.0f;
-
- mController.moveWindowMagnifier(offsetX, offsetY);
+ mInstrumentation.runOnMainSync(()-> mController.moveWindowMagnifier(offsetX, offsetY));
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY);
@@ -726,8 +745,8 @@
final float offsetY =
(float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- 1.0f;
-
- 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);
@@ -742,8 +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
- 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);
@@ -755,9 +776,11 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
- mAnimationCallback);
- advanceTimeBy(mWaitAnimationDuration);
+ mInstrumentation.runOnMainSync(() -> {
+ mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@@ -774,24 +797,49 @@
}
private void enableWindowMagnificationWithoutAnimation() {
- 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) {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
- advanceTimeBy(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) {
- resetMockObjects();
- mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
- advanceTimeBy(duration);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
+ advanceTimeBy(duration);
+ });
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
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/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/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
index e16b8d4..f4d2cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -19,15 +19,15 @@
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.
+ * 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.
+ * 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)) {
@@ -37,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/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 fd1e2c7..da448aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -74,6 +74,15 @@
}
@Test
+ fun notifyProjectionCancelled_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+
+ logger.notifyProjectionRequestCancelled(hostUid)
+
+ verify(service).notifyPermissionRequestCancelled(hostUid)
+ }
+
+ @Test
fun notifyAppSelectorDisplayed_forwardsToService() {
val hostUid = 654
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 5255f71..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
@@ -214,7 +213,7 @@
@Test
fun init_firstStart_logsAppSelectorDisplayed() {
val hostUid = 123456789
- val controller = createController(isFirstStart = true, hostUid)
+ val controller = createController(isFirstStart = true, hostUid)
controller.init()
@@ -231,6 +230,15 @@
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) {
whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
}
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/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/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 9b85012..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,22 +16,21 @@
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.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
-import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -77,9 +76,6 @@
testScope.runTest {
assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
- underTest.onLifecycle(QSTileLifecycle.ALIVE)
- underTest.onUserIdChanged(1)
-
assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
underTest.state.launchIn(backgroundScope)
@@ -87,20 +83,22 @@
assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
assertThat(fakeQSTileDataInteractor.dataRequests.first())
- .isEqualTo(FakeQSTileDataInteractor.DataRequest(1))
+ .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0)))
}
private fun createViewModel(
scope: TestScope,
config: QSTileConfig = TEST_QS_TILE_CONFIG,
): QSTileViewModel =
- BaseQSTileViewModel(
+ 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,
@@ -114,12 +112,6 @@
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/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 bf12d7d..fd38139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -26,6 +26,7 @@
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
@@ -41,7 +42,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -57,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
@@ -72,7 +73,8 @@
controller,
starter,
userContextProvider,
- onStartRecordingClicked
+ onStartRecordingClicked,
+ mediaProjectionMetricsLogger,
)
dialog.onCreate(null)
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
@@ -149,6 +151,28 @@
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()
}
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/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/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 2b3fd34..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;
@@ -37,12 +39,11 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.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;
@@ -71,12 +72,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- featureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mListener = new NotificationListener(
mContext,
- featureFlags,
mNotificationManager,
new SilentNotificationStatusIconsVisibilityInteractor(
new NotificationListenerSettingsRepository()),
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/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/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index a736182..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,8 +19,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.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
@@ -30,6 +29,7 @@
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
@@ -39,6 +39,7 @@
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.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations.initMocks
@@ -59,15 +60,18 @@
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
- val featureFlags =
- FakeFeatureFlagsClassic().apply { setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR) }
-
@Before
fun setUp() {
initMocks(this)
+ 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(
- featureFlags,
groupExpansionManagerImpl,
notificationIconAreaController,
renderListInteractor,
@@ -76,19 +80,18 @@
afterRenderListListener = withArgCaptor {
verify(pipeline).addOnAfterRenderListListener(capture())
}
- entry = NotificationEntryBuilder().setSection(section).build()
}
@Test
fun testUpdateNotificationIcons() {
- featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, false)
+ setUp(NotificationIconContainerRefactor.FLAG_NAME to false)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
}
@Test
fun testSetRenderedListOnInteractor() {
- featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, true)
+ setUp(NotificationIconContainerRefactor.FLAG_NAME to true)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
}
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 3dafb23..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;
@@ -84,6 +85,7 @@
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;
@@ -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/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 41eaf85..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;
@@ -163,6 +164,7 @@
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;
@@ -185,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;
@@ -200,6 +200,8 @@
import javax.inject.Provider;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -335,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.
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 472709c..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;
@@ -43,7 +45,6 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -53,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;
@@ -105,7 +107,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags,
mHeadsUpManager, mBatteryController, mScrimController,
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 529e2c9..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;
@@ -35,7 +37,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeHeadsUpTracker;
@@ -47,6 +48,7 @@
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;
@@ -90,7 +92,7 @@
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
- mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+ setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME);
mTestHelper = new NotificationTestHelper(
mContext,
mDependency,
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/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/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 9a77f0c..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,8 +37,6 @@
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;
@@ -67,10 +60,8 @@
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.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
@@ -283,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
@@ -323,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());
}
@@ -339,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());
}
@@ -356,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
@@ -367,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());
}
@@ -381,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());
}
@@ -395,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());
}
@@ -409,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
@@ -418,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());
}
@@ -451,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
@@ -507,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);
@@ -713,8 +718,6 @@
mock(NotificationIconContainerStatusBarViewModel.class),
mock(ConfigurationState.class),
mock(ConfigurationController.class),
- mock(DozeParameters.class),
- mock(ScreenOffAnimationController.class),
mock(StatusBarNotificationIconViewStore.class),
mock(DemoModeController.class));
}
@@ -729,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/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/kotlin/LayoutInflaterUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt
deleted file mode 100644
index 1c8465a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt
+++ /dev/null
@@ -1,137 +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.util.kotlin
-
-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.util.view.reinflateAndBindLatest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class LayoutInflaterUtilTest : SysuiTestCase() {
- @JvmField @Rule val mockito = MockitoJUnit.rule()
-
- private var inflationCount = 0
- private var callbackCount = 0
- @Mock private lateinit var disposableHandle: DisposableHandle
-
- inner class TestLayoutInflater : LayoutInflater(context) {
- 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
- }
- }
-
- val underTest = TestLayoutInflater()
-
- @After
- fun cleanUp() {
- inflationCount = 0
- callbackCount = 0
- }
-
- @Test
- fun testReinflateAndBindLatest_inflatesWithoutEmission() = runTest {
- backgroundScope.launch {
- underTest.reinflateAndBindLatest(
- resource = 0,
- root = null,
- attachToRoot = false,
- emptyFlow<Unit>()
- ) {
- callbackCount++
- null
- }
- }
-
- // Inflates without an emission
- runCurrent()
- assertThat(inflationCount).isEqualTo(1)
- assertThat(callbackCount).isEqualTo(1)
- }
-
- @Test
- fun testReinflateAndBindLatest_reinflatesOnEmission() = runTest {
- val observable = MutableSharedFlow<Unit>()
- val flow = observable.asSharedFlow()
- backgroundScope.launch {
- underTest.reinflateAndBindLatest(
- resource = 0,
- root = null,
- attachToRoot = false,
- flow
- ) {
- callbackCount++
- null
- }
- }
-
- listOf(1, 2, 3).forEach { count ->
- runCurrent()
- assertThat(inflationCount).isEqualTo(count)
- assertThat(callbackCount).isEqualTo(count)
- observable.emit(Unit)
- }
- }
-
- @Test
- fun testReinflateAndBindLatest_disposesOnCancel() = runTest {
- val job = launch {
- underTest.reinflateAndBindLatest(
- resource = 0,
- root = null,
- attachToRoot = false,
- emptyFlow()
- ) {
- callbackCount++
- disposableHandle
- }
- }
-
- runCurrent()
- job.cancelAndJoin()
- verify(disposableHandle).dispose()
- }
-}
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/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 6ef812b..e6e6b7b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -17,7 +17,6 @@
import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -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;
/**
@@ -86,7 +80,7 @@
public TestableDependency mDependency;
private Instrumentation mRealInstrumentation;
- private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
+ private SysuiTestDependency mSysuiDependency;
@Before
public void SysuiSetup() throws Exception {
@@ -97,18 +91,8 @@
// 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 +104,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 +123,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 +152,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/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/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 5593596..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,6 +16,7 @@
package com.android.systemui.qs.tiles.base.interactor
+import android.os.UserHandle
import javax.annotation.CheckReturnValue
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -38,16 +39,16 @@
fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
- override fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<T> {
- mutableDataRequests.add(DataRequest(userId))
+ override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> {
+ mutableDataRequests.add(DataRequest(user))
return triggers.flatMapLatest { dataFlow }
}
- override fun availability(userId: Int): Flow<Boolean> {
- mutableAvailabilityRequests.add(AvailabilityRequest(userId))
+ override fun availability(user: UserHandle): Flow<Boolean> {
+ mutableAvailabilityRequests.add(AvailabilityRequest(user))
return availabilityFlow
}
- data class DataRequest(val userId: Int)
- data class AvailabilityRequest(val userId: Int)
+ data class DataRequest(val user: UserHandle)
+ data class AvailabilityRequest(val user: UserHandle)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/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/core/Android.bp b/services/core/Android.bp
index 961e9d3..3985a0e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -155,6 +155,7 @@
"service-permission.stubs.system_server",
"service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
+ "device_policy_aconfig_flags_lib",
],
plugins: ["ImmutabilityAnnotationProcessor"],
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 3af0e8c..15fc2dc 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3007,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;
@@ -3231,12 +3231,12 @@
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
- public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+ public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
- super.createUserKey_enforcePermission();
+ super.createUserStorageKeys_enforcePermission();
try {
- mVold.createUserKey(userId, serialNumber, ephemeral);
+ mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
// Since the user's CE key was just created, the user's CE storage is now unlocked.
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
@@ -3248,12 +3248,12 @@
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
- public void destroyUserKey(int userId) {
+ public void destroyUserStorageKeys(int userId) {
- super.destroyUserKey_enforcePermission();
+ super.destroyUserStorageKeys_enforcePermission();
try {
- mVold.destroyUserKey(userId);
+ mVold.destroyUserStorageKeys(userId);
// Since the user's CE key was just destroyed, the user's CE storage is now locked.
synchronized (mLock) {
mCeUnlockedUsers.remove(userId);
@@ -3266,21 +3266,22 @@
/* Only for use by LockSettingsService */
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
- public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
- super.setUserKeyProtection_enforcePermission();
+ public void setCeStorageProtection(@UserIdInt int userId, byte[] secret)
+ throws RemoteException {
+ super.setCeStorageProtection_enforcePermission();
- mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
+ mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
}
/* Only for use by LockSettingsService */
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
- public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
- throws RemoteException {
- super.unlockUserKey_enforcePermission();
+ public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
+ throws RemoteException {
+ super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
@@ -3289,23 +3290,22 @@
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
- public void lockUserKey(int userId) {
- // Do not lock user 0 data for headless system user
- super.lockUserKey_enforcePermission();
+ public void lockCeStorage(int userId) {
+ super.lockCeStorage_enforcePermission();
+ // Never lock the CE storage of a headless system user.
if (userId == UserHandle.USER_SYSTEM
&& UserManager.isHeadlessSystemUserMode()) {
throw new IllegalArgumentException("Headless system user data cannot be locked..");
}
-
- if (!isUserKeyUnlocked(userId)) {
+ if (!isCeStorageUnlocked(userId)) {
Slog.d(TAG, "User " + userId + "'s CE storage is already locked");
return;
}
try {
- mVold.lockUserKey(userId);
+ mVold.lockCeStorage(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
return;
@@ -3317,7 +3317,7 @@
}
@Override
- public boolean isUserKeyUnlocked(int userId) {
+ public boolean isCeStorageUnlocked(int userId) {
synchronized (mLock) {
return mCeUnlockedUsers.contains(userId);
}
@@ -3719,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);
}
@@ -3846,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);
}
@@ -3914,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) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef67cbe..e88d0c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2299,7 +2299,7 @@
return;
}
// TODO(b/148767783): should we check all profiles under user0?
- UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
BackgroundThread.getExecutor());
}
@@ -4648,7 +4648,7 @@
// We carefully use the same state that PackageManager uses for
// filtering, since we use this flag to decide if we need to install
// providers when user is unlocked later
- app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
+ app.setUnlocked(StorageManager.isCeStorageUnlocked(app.userId));
}
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
@@ -9718,9 +9718,29 @@
public 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);
}
@@ -9729,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);
}
@@ -9742,6 +9770,7 @@
}
final int callingUid = Binder.getCallingUid();
+ mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
}
@Override
@@ -10043,6 +10072,8 @@
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
+ mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ pw.println("-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
if (dumpPackage == null) {
@@ -10439,6 +10470,12 @@
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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f04198e..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();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index a57a785..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,10 +137,12 @@
"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",
@@ -152,9 +155,12 @@
"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 0dd579f..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;
}
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/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/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 57b2c24..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() {
@@ -3044,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/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/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 58927d1..893ed61 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -470,6 +470,11 @@
}
@VisibleForTesting
+ void notifyPermissionRequestCancelled(int hostUid) {
+ mMediaProjectionMetricsLogger.logProjectionPermissionRequestCancelled(hostUid);
+ }
+
+ @VisibleForTesting
void notifyAppSelectorDisplayed(int hostUid) {
mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid);
}
@@ -852,19 +857,6 @@
@Override // Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void notifyPermissionRequestStateChange(int hostUid, int state,
- int sessionCreationSource) {
- notifyPermissionRequestStateChange_enforcePermission();
- final long token = Binder.clearCallingIdentity();
- try {
- mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
- @EnforcePermission(MANAGE_MEDIA_PROJECTION)
public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
notifyPermissionRequestInitiated_enforcePermission();
final long token = Binder.clearCallingIdentity();
@@ -890,6 +882,18 @@
@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();
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 55a30bf..d7fefeb 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -118,6 +118,23 @@
}
/**
+ * 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_CANCELLED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ FrameworkStatsLog
+ .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
* Logs that the app selector dialog is shown for the user.
*
* @param hostUid UID of the package that initiates MediaProjection.
@@ -174,23 +191,6 @@
}
}
- public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
- write(hostUid, state, sessionCreationSource);
- }
-
- 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);
- }
-
private void write(
int sessionId,
int state,
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/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 510c06e..3ed9f02 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3556,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()) {
@@ -3571,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;
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 7d716a68..2951ef6 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -59,7 +59,6 @@
import static com.android.server.pm.PackageManagerService.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();
}
@@ -2379,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());
@@ -2399,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);
}
/**
@@ -2521,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
@@ -2832,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) {
@@ -2847,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 c260be9..e857e05 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -506,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;
@@ -758,6 +761,10 @@
}
}
+ private boolean isPackageArchived(@NonNull String packageName, UserHandle user) {
+ return !getApplicationInfoForArchivedApp(packageName, user).isEmpty();
+ }
+
@NonNull
private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp(
@Nullable String packageName, UserHandle user) {
@@ -969,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);
}
@@ -1439,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());
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 52655c4..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;
@@ -1673,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),
@@ -1884,7 +1880,6 @@
mProcessLoggingHandler = testParams.processLoggingHandler;
mProtectedPackages = testParams.protectedPackages;
mSeparateProcesses = testParams.separateProcesses;
- mViewCompiler = testParams.viewCompiler;
mRequiredVerifierPackages = testParams.requiredVerifierPackages;
mRequiredInstallerPackage = testParams.requiredInstallerPackage;
mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
@@ -2047,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();
@@ -3363,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/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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 154ee6e..978d8e4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1427,7 +1427,7 @@
}
final boolean needToShowConfirmCredential = !dontAskCredential
&& mLockPatternUtils.isSecure(userId)
- && (!hasUnifiedChallenge || !StorageManager.isUserKeyUnlocked(userId));
+ && (!hasUnifiedChallenge || !StorageManager.isCeStorageUnlocked(userId));
if (needToShowConfirmCredential) {
if (onlyIfCredentialNotRequired) {
return false;
@@ -5095,9 +5095,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
@@ -5899,17 +5899,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
@@ -7177,9 +7178,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_UNLOCKING)
|| (state == UserState.STATE_RUNNING_UNLOCKED);
@@ -7196,9 +7197,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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b439681..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;
@@ -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*/);
+ }
+ }
}
/**
@@ -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/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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fd8f6bf..bdab4d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9317,8 +9317,9 @@
final 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/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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4fa6e29..f314900 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1622,7 +1622,17 @@
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;
}
@@ -4706,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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2281395..c81105a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2051,6 +2051,8 @@
}
transitionController.deferTransitionReady();
+ Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip");
+ transitionController.waitFor(pipChangesApplied);
mService.deferWindowLayout();
try {
// This will change the root pinned task's windowing mode to its original mode, ensuring
@@ -2235,6 +2237,7 @@
}
} finally {
transitionController.continueTransitionReady();
+ pipChangesApplied.meet();
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6c31e3e..fa104bb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3643,7 +3643,7 @@
*/
TaskFragmentParentInfo getTaskFragmentParentInfo() {
return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
- shouldBeVisible(null /* starting */));
+ shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity());
}
@Override
@@ -4715,7 +4715,7 @@
}
if (isAttached()) {
setWindowingMode(WINDOWING_MODE_UNDEFINED);
- moveTaskToBackInner(this);
+ moveTaskToBackInner(this, null /* transition */);
}
if (top.isAttached()) {
top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
@@ -5718,24 +5718,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);
@@ -5752,6 +5755,9 @@
if (mTransitionController.isShellTransitionsEnabled()) {
mAtmService.continueWindowLayout();
}
+ if (transition != null) {
+ movedToBack.meet();
+ }
}
ActivityRecord topActivity = getDisplayArea().topRunningActivity();
Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
@@ -6056,6 +6062,11 @@
// Non-root task position changed.
mRootWindowContainer.invalidateTaskLayers();
}
+
+ if (child.asActivityRecord() != null) {
+ // Send for TaskFragmentParentInfo#hasDirectActivity change.
+ sendTaskFragmentParentInfoChangedIfNeeded();
+ }
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 82d3424..2fc531a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1049,6 +1049,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 -> {
@@ -1059,6 +1063,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()
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/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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5ed6caf..eb60aab 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -305,9 +305,12 @@
// This is a direct call from shell, so the entire transition lifecycle is
// contained in the provided transaction if provided. Thus, we can setReady
// immediately after apply.
+ final Transition.ReadyCondition wctApplied =
+ new Transition.ReadyCondition("start WCT applied");
final boolean needsSetReady = t != null;
final Transition nextTransition = new Transition(type, 0 /* flags */,
mTransitionController, mService.mWindowManager.mSyncEngine);
+ nextTransition.mReadyTracker.add(wctApplied);
nextTransition.calcParallelCollectType(wct);
mTransitionController.startCollectOrQueue(nextTransition,
(deferred) -> {
@@ -315,6 +318,7 @@
nextTransition.mLogger.mStartWCT = wct;
applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
deferred);
+ wctApplied.meet();
if (needsSetReady) {
// TODO(b/294925498): Remove this once we have accurate ready
// tracking.
@@ -329,6 +333,15 @@
});
return nextTransition.getToken();
}
+ // Currently, application of wct can span multiple looper loops (ie.
+ // waitAsyncStart), so add a condition to ensure that it finishes applying.
+ final Transition.ReadyCondition wctApplied;
+ if (t != null) {
+ wctApplied = new Transition.ReadyCondition("start WCT applied");
+ transition.mReadyTracker.add(wctApplied);
+ } else {
+ wctApplied = null;
+ }
// The transition already started collecting before sending a request to shell,
// so just start here.
if (!transition.isCollecting() && !transition.isForcePlaying()) {
@@ -336,6 +349,9 @@
+ " means Shell took too long to respond to a request. WM State may be"
+ " incorrect now, please file a bug");
applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+ if (wctApplied != null) {
+ wctApplied.meet();
+ }
return transition.getToken();
}
transition.mLogger.mStartWCT = wct;
@@ -344,11 +360,17 @@
synchronized (mService.mGlobalLock) {
transition.start();
applyTransaction(wct, -1 /* syncId */, transition, caller);
+ if (wctApplied != null) {
+ wctApplied.meet();
+ }
}
});
} else {
transition.start();
applyTransaction(wct, -1 /* syncId */, transition, caller);
+ if (wctApplied != null) {
+ wctApplied.meet();
+ }
}
// Since the transition is already provided, it means WMCore is determining the
// "readiness lifecycle" outside the provided transaction, so don't set ready here.
@@ -1159,9 +1181,13 @@
}
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
- if (container == null || container.asDisplayArea() == null
- || !container.isAttached()) {
- Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+ if (container == null || !container.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ if (container.asTask() == null && container.asDisplayArea() == null) {
+ Slog.e(TAG, "Cannot set always-on-top on non-task or non-display area: "
+ container);
break;
}
diff --git a/services/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/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/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/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/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/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 4e6dd06..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
@@ -674,6 +674,17 @@
}
@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 =
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 410604f..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
@@ -18,6 +18,7 @@
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;
@@ -452,6 +453,63 @@
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() {
verify(mFrameworkStatsLogWrapper)
.write(
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/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/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/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..cd3fef6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1646,6 +1646,28 @@
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/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/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 0b70b40e..042b2a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8806,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";
@@ -8874,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";
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/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/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/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