Merge "fix(window magnification): fix changeMagnificationSize_expectedWindowSize presubmit error" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index db51537..75fb215 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -14,6 +14,7 @@
aconfig_srcjars = [
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
@@ -599,3 +600,16 @@
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..986a071 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,29 @@
"android.hardware.vibrator-V1.3-java",
"framework-protos",
],
+ flags: [
+ "--api-lint-ignore-prefix android.icu.",
+ "--api-lint-ignore-prefix java.",
+ "--api-lint-ignore-prefix junit.",
+ "--api-lint-ignore-prefix org.",
+ "--error NoSettingsProvider",
+ "--error UnhiddenSystemApi",
+ "--error UnflaggedApi",
+ "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*",
+ "--hide BroadcastBehavior",
+ "--hide CallbackInterface",
+ "--hide DeprecationMismatch",
+ "--hide HiddenSuperclass",
+ "--hide HiddenTypeParameter",
+ "--hide MissingPermission",
+ "--hide RequiresPermission",
+ "--hide SdkConstant",
+ "--hide Todo",
+ "--hide UnavailableSymbol",
+ "--hide-package android.audio.policy.configuration.V7_0",
+ "--hide-package com.android.server",
+ "--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
+ ],
filter_packages: packages_to_document,
high_mem: true, // Lots of sources => high memory use, see b/170701554
installable: false,
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 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/core/api/current.txt b/core/api/current.txt
index c3639b5..3f1f720 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12889,7 +12889,7 @@
field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
- field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
+ field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -17713,12 +17713,14 @@
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
+ field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_STYLE_AUTO = 5; // 0x5
field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4
field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
+ field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; // 0x2
field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
@@ -33048,7 +33050,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);
}
@@ -44502,7 +44504,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 29f4f97..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);
@@ -3487,7 +3489,7 @@
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final String TETHERING_SERVICE = "tethering";
- field public static final String THREAD_NETWORK_SERVICE = "thread_network";
+ field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_SERVICE = "thread_network";
field public static final String TIME_MANAGER_SERVICE = "time_manager";
field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a0b6fb7..796c800 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3491,10 +3491,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..6aefa38
--- /dev/null
+++ b/core/java/android/app/smartspace/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.smartspace.flags"
+
+flag {
+ name: "remote_views"
+ namespace: "sysui_integrations"
+ description: "Flag to enable the FlaggedApi to include RemoteViews in SmartspaceTarget"
+ bug: "300157758"
+}
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.aidl b/core/java/android/app/usage/ParcelableUsageEventList.aidl
new file mode 100644
index 0000000..1652996
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+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/Context.java b/core/java/android/content/Context.java
index 7afc29c..1c917ee 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -23,6 +23,7 @@
import android.annotation.ColorRes;
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -860,12 +861,23 @@
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
* <p>
- * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+ * After {@link Build.VERSION_CODES#S}, registering the ComponentCallbacks to Context created
* via {@link #createWindowContext(int, Bundle)} or
* {@link #createWindowContext(Display, int, Bundle)} will receive
* {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
* than its base application. It is helpful if you want to handle UI components that
* associated with the Window Context when the Window Context has configuration changes.</p>
+ * <p>
+ * After {@link Build.VERSION_CODES#TIRAMISU}, registering the ComponentCallbacks to
+ * {@link Activity} context will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from
+ * {@link Activity#onConfigurationChanged(Configuration)} rather than its base application.</p>
+ * <p>
+ * After {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, registering the ComponentCallbacks to
+ * {@link android.inputmethodservice.InputMethodService} will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from InputmethodService
+ * rather than its base application. It is helpful if you want to handle UI components when the
+ * IME has configuration changes.</p>
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
@@ -4773,6 +4785,7 @@
* @see android.net.thread.ThreadNetworkManager
* @hide
*/
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled")
@SystemApi
public static final String THREAD_NETWORK_SERVICE = "thread_network";
diff --git a/core/java/android/content/pm/ArchivedActivity.java b/core/java/android/content/pm/ArchivedActivity.java
index 5139e2d..9e49c9e 100644
--- a/core/java/android/content/pm/ArchivedActivity.java
+++ b/core/java/android/content/pm/ArchivedActivity.java
@@ -79,14 +79,14 @@
* @hide
*/
public static Bitmap drawableToBitmap(Drawable drawable) {
- return drawableToBitmap(drawable, /* maxIconSize= */ Integer.MAX_VALUE);
+ return drawableToBitmap(drawable, /* iconSize= */ 0);
}
/**
- * Same as above, but.
+ * Same as above, but scale the resulting image to fit iconSize.
* @hide
*/
- public static Bitmap drawableToBitmap(Drawable drawable, int maxIconSize) {
+ public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
@@ -106,8 +106,8 @@
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
- if (bitmap.getWidth() > maxIconSize || bitmap.getHeight() > maxIconSize) {
- var scaledBitmap = Bitmap.createScaledBitmap(bitmap, maxIconSize, maxIconSize, true);
+ if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
+ var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
if (scaledBitmap != bitmap) {
bitmap.recycle();
}
@@ -118,7 +118,6 @@
/**
* Compress bitmap to PNG format.
- * The bitmap is going to be recycled.
* @hide
*/
public static byte[] bytesFromBitmap(Bitmap bitmap) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6d4276d..ad7dd51 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3778,6 +3778,7 @@
* The device is capable of communicating with other devices via
* <a href="https://www.threadgroup.org">Thread</a> networking protocol.
*/
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled")
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
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/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 2b5f5ee..b2a2819 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;
@@ -412,6 +411,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 +481,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 +512,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 +1187,7 @@
Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
event));
}
- handleDisplayEvent(displayId, event);
+ handleDisplayEvent(displayId, event, false /* forceUpdate */);
}
}
@@ -1197,87 +1208,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;
}
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/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..10b9e3a 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -34,3 +34,10 @@
description: "Introduce a constant as maximum value of bugreport mode."
bug: "305067125"
}
+
+flag {
+ name: "adpf_prefer_power_efficiency"
+ namespace: "game"
+ description: "Guards the ADPF power efficiency API"
+ bug: "288117936"
+}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 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/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/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 517ae4f..5f6a9bd 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -457,12 +457,21 @@
} else {
hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
}
+ LineBreakConfig config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && pct.getParagraphCount() != 1) {
+ // If the text has multiple paragraph, resolve line break word style auto to none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
ArrayList<ParagraphInfo> result = new ArrayList<>();
for (int i = 0; i < pct.getParagraphCount(); ++i) {
final int paraStart = pct.getParagraphStart(i);
final int paraEnd = pct.getParagraphEnd(i);
result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd,
+ params.getTextPaint(), config, pct, paraStart, paraEnd,
params.getTextDirection(), hyphenationMode, computeLayout, true,
pct.getMeasuredParagraph(i), null /* no recycle */)));
}
@@ -489,6 +498,7 @@
hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
}
+ LineBreakConfig config = null;
int paraEnd = 0;
for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
@@ -500,8 +510,21 @@
paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
}
+ if (config == null) {
+ config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && !(paraStart == start && paraEnd == end)) {
+ // If the text has multiple paragraph, resolve line break word style auto to
+ // none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ }
+
result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd,
+ params.getTextPaint(), config, text, paraStart, paraEnd,
params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
null /* no hint */,
null /* no recycle */)));
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index cf36d89..43c38f3 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -68,3 +68,10 @@
description: "Feature flag for deprecating UI fonts. By setting true for this feature flag, the elegant text height of will be turned on by default unless explicitly setting it to false by attribute or Java API call."
bug: "279646685"
}
+
+flag {
+ name: "word_style_auto"
+ namespace: "text"
+ description: "A feature flag that implements line break word style auto."
+ bug: "280005585"
+}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 1ec7c41..17a3a12 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -28,6 +28,7 @@
import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -469,8 +470,10 @@
}
addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
- final boolean visible = mPendingFraction == 0 && source != null
- ? source.isVisible()
+ // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is animated from
+ // the hidden state.
+ final boolean visible = mPendingFraction == 0
+ ? mAnimationType != ANIMATION_TYPE_SHOW
: !mFinished || mShownOnFinish;
if (outState != null && source != null) {
diff --git a/core/java/android/view/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/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/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/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/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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index cc73ece..35498b7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2365,6 +2365,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "33989965": {
+ "message": " Met condition %s for #%d (%d left)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"34106798": {
"message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?",
"level": "VERBOSE",
@@ -4483,6 +4489,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "2053743391": {
+ "message": " Add condition %s for #%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"2060978050": {
"message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
"level": "WARN",
@@ -4543,6 +4555,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "2124732293": {
+ "message": "#%d: Met condition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"2128917433": {
"message": "onProposedRotationChanged, rotation=%d",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index dc1773b..62195856 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,11 +17,17 @@
package android.graphics.text;
import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+import android.os.LocaleList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,6 +43,14 @@
public final class LineBreakConfig {
/**
+ * A feature ID for automatic line break word style.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long WORD_STYLE_AUTO = 280005585L;
+
+ /**
* No hyphenation preference is specified.
*
* <p>
@@ -112,8 +126,11 @@
* </pre>
*
* <p>
- * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
- * layout/rendering.
+ * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API
+ * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+ * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK
+ * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+ * used for text layout/rendering.
*/
@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
@@ -154,10 +171,29 @@
@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
+ /**
+ * A special value for the line breaking style option.
+ *
+ * <p>
+ * The auto option for the line break style set the line break style based on the locale of the
+ * text rendering context. You can specify the context locale by
+ * {@link android.widget.TextView#setTextLocales(LocaleList)} or
+ * {@link android.graphics.Paint#setTextLocales(LocaleList)}.
+ *
+ * <p>
+ * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+ * - If at least one locale in the locale list contains Japanese script, this option is
+ * equivalent to {@link #LINE_BREAK_STYLE_STRICT}.
+ * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}.
+ */
+ @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+ public static final int LINE_BREAK_STYLE_AUTO = 5;
+
/** @hide */
@IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
- LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK
+ LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK,
+ LINE_BREAK_STYLE_AUTO
})
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakStyle {}
@@ -183,8 +219,11 @@
* // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
* </pre>
*
- * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for
- * text layout/rendering.
+ * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is
+ * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+ * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target
+ * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+ * used for text layout/rendering.
*/
@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
@@ -204,9 +243,29 @@
*/
public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
+ /**
+ * A special value for the line breaking word style option.
+ *
+ * <p>
+ * The auto option for the line break word style does some heuristics based on locales and line
+ * count.
+ *
+ * <p>
+ * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+ * - If at least one locale in the locale list contains Korean script, this option is equivalent
+ * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}.
+ * - If not, then if at least one locale in the locale list contains Japanese script, this
+ * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line
+ * count is less than 5 lines.
+ * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}.
+ */
+ @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+ public static final int LINE_BREAK_WORD_STYLE_AUTO = 2;
+
/** @hide */
@IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
- LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED
+ LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED,
+ LINE_BREAK_WORD_STYLE_AUTO
})
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakWordStyle {}
@@ -425,11 +484,13 @@
* @hide
*/
public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
+ final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+ ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
if (config == null) {
- return LINE_BREAK_STYLE_NONE;
+ return defaultStyle;
}
return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
- ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle;
+ ? defaultStyle : config.mLineBreakStyle;
}
/**
@@ -451,11 +512,13 @@
*/
public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
@Nullable LineBreakConfig config) {
+ final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+ ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
if (config == null) {
- return LINE_BREAK_WORD_STYLE_NONE;
+ return defaultWordStyle;
}
return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
- ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle;
+ ? defaultWordStyle : config.mLineBreakWordStyle;
}
/**
diff --git a/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..87e0b28 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -22,6 +22,7 @@
android:orientation="vertical">
<LinearLayout
+ android:id="@+id/app_info_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
@@ -66,6 +67,7 @@
</LinearLayout>
<LinearLayout
+ android:id="@+id/windowing_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -116,6 +118,7 @@
</LinearLayout>
<LinearLayout
+ android:id="@+id/more_actions_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bb262d3..3aed9eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -455,7 +455,7 @@
}
/**
- * Create and display handle menu window
+ * Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
@@ -466,15 +466,18 @@
.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..6391518 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -71,10 +71,13 @@
private int mMenuHeight;
private int mMenuWidth;
+ private final int mCaptionHeight;
+
HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+ Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+ int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -86,6 +89,7 @@
mAppIcon = 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.
*/
@@ -322,6 +342,7 @@
private int mCaptionX;
private int mCaptionY;
private boolean mShowWindowingPill;
+ private int mCaptionHeight;
Builder(@NonNull WindowDecoration parent) {
@@ -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..400dec4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -48,7 +48,6 @@
}
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/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/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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4e2fad0..95d7039 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(
@@ -2960,6 +2888,7 @@
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/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/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/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..2e5bc47 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -629,7 +629,7 @@
<!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
<!-- QuickSettings: Bluetooth dialog subtitle [CHAR LIMIT=NONE]-->
- <string name="quick_settings_bluetooth_tile_subtitle">Tap a device to connect</string>
+ <string name="quick_settings_bluetooth_tile_subtitle">Tap to connect or disconnect a device </string>
<!-- QuickSettings: Bluetooth dialog pair new devices [CHAR LIMIT=NONE]-->
<string name="pair_new_bluetooth_devices">Pair new device</string>
<!-- QuickSettings: Bluetooth dialog see all devices [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3c8301f..8efe165 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,21 @@
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.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 +75,8 @@
import javax.inject.Inject;
+import kotlinx.coroutines.DisposableHandle;
+
/**
* Injectable controller for {@link KeyguardClockSwitch}.
*/
@@ -84,6 +93,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 +122,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 +169,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 +346,8 @@
int getNotificationIconAreaHeight() {
if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
return 0;
+ } else if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
} else {
return mNotificationIconAreaController.getHeight();
}
@@ -533,7 +565,27 @@
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
- mNotificationIconAreaController.setupAodIcons(nic);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ 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/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/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..8a9ea25c 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,39 @@
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.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 +86,23 @@
return
}
- notificationIconAreaController.setupAodIcons(nic)
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ 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 +134,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/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
similarity index 63%
rename from core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
rename to packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
index 7a0f438..c73afe8 100644
--- a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothTileDialogLog.kt
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package android.hardware.biometrics;
+package com.android.systemui.log.dagger
-/**
- * Communication channel to propagate biometric prompt status. Implementation of this interface
- * should be registered in BiometricService#registerBiometricPromptStatusListener.
- * @hide
- */
-oneway interface IBiometricPromptStatusListener {
- void onBiometricPromptShowing();
- void onBiometricPromptIdle();
-}
\ No newline at end of file
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for Bluetooth QS tile dialog related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BluetoothTileDialogLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index fd6b3f1..aecfdaa 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -552,4 +552,12 @@
public 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/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 4aad6a0..35c2b06 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.
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/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/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index 246933a..41b42e3 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,14 @@
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.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 +35,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,9 +65,7 @@
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
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..2a7d087 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,26 @@
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.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
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.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.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.awaitCancellation
import kotlinx.coroutines.launch
@@ -75,14 +82,31 @@
fun bind(
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ dozeParameters: DozeParameters,
falsingManager: FalsingManager,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
notificationIconAreaController: NotificationIconAreaController,
+ screenOffAnimationController: ScreenOffAnimationController,
+ shelfIconViewStore: ShelfNotificationIconViewStore,
) {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
- // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
- notificationIconAreaController.setShelfIcons(shelfIcons)
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ NotificationIconContainerViewBinder.bind(
+ shelfIcons,
+ viewModel.icons,
+ configuration,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ 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..b770b83 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,10 @@
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final ConfigurationState mConfigurationState;
+ private final DozeParameters mDozeParameters;
+ private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final ShelfNotificationIconViewStore mShelfIconViewStore;
private View mLongPressedView;
@@ -674,7 +682,10 @@
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ ConfigurationState configurationState, DozeParameters dozeParameters,
+ ScreenOffAnimationController screenOffAnimationController,
+ ShelfNotificationIconViewStore shelfIconViewStore) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mStackStateLogger = stackLogger;
@@ -724,6 +735,10 @@
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mConfigurationState = configurationState;
+ mDozeParameters = dozeParameters;
+ mScreenOffAnimationController = screenOffAnimationController;
+ mShelfIconViewStore = shelfIconViewStore;
mView.passSplitShadeStateController(splitShadeStateController);
updateResources();
setUpView();
@@ -832,8 +847,10 @@
mViewModel.ifPresent(
vm -> NotificationListViewBinder
- .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController,
- mConfigurationController));
+ .bind(mView, vm, mConfigurationState, mConfigurationController,
+ mDozeParameters, mFalsingManager, mFeatureFlags,
+ mNotifIconAreaController, mScreenOffAnimationController,
+ 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..69b96fa 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,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.reinflateAndBindLatest
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
@@ -24,16 +26,15 @@
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.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
-import com.android.systemui.statusbar.policy.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 +42,14 @@
fun bind(
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ dozeParameters: DozeParameters,
falsingManager: FalsingManager,
featureFlags: FeatureFlagsClassic,
iconAreaController: NotificationIconAreaController,
- configurationController: ConfigurationController,
+ screenOffAnimationController: ScreenOffAnimationController,
+ shelfIconViewStore: ShelfNotificationIconViewStore,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -52,28 +57,27 @@
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
+ configuration,
+ configurationController,
+ dozeParameters,
falsingManager,
featureFlags,
- iconAreaController
+ iconAreaController,
+ screenOffAnimationController,
+ 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/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/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..153f3f7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -35,10 +35,11 @@
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.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.log.LogBuffer;
@@ -50,12 +51,18 @@
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.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;
@@ -166,6 +173,7 @@
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mExecutor = new FakeExecutor(new FakeSystemClock());
mFakeFeatureFlags = new FakeFeatureFlags();
+ mFakeFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false);
@@ -176,12 +184,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/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/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/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/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/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..7931e9e 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;
@@ -219,7 +218,6 @@
powerInteractor,
featureFlags,
sceneContainerFlags,
- new FakeDeviceEntryRepository(),
new FakeKeyguardBouncerRepository(),
configurationRepository,
mShadeRepository,
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/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/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..2b944c3 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,14 +85,17 @@
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -716,8 +720,11 @@
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
- new ResourcesSplitShadeStateController()
- );
+ new ResourcesSplitShadeStateController(),
+ mock(ConfigurationState.class),
+ mock(DozeParameters.class),
+ mock(ScreenOffAnimationController.class),
+ mock(ShelfNotificationIconViewStore.class));
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
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/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/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/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/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/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/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/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/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..a1d2e14 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
@@ -992,8 +1012,8 @@
/*package*/ void registerStrategyPreferredDevicesDispatcher(
- @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
- mPrefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mPrefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1002,8 +1022,8 @@
}
/*package*/ void registerStrategyNonDefaultDevicesDispatcher(
- @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
- mNonDefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mNonDefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1084,8 +1104,8 @@
}
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
- @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
- mDevRoleCapturePresetDispatchers.register(dispatcher);
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+ mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1414,6 +1434,8 @@
updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
if (!connect) {
purgeDevicesRoles_l();
+ } else {
+ addAudioDeviceInInventoryIfNeeded(attributes);
}
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1702,6 +1724,7 @@
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2006,9 +2029,9 @@
final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
-
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+ mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
@@ -2018,6 +2041,7 @@
mDeviceBroker.postApplyVolumeOnDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
+ addAudioDeviceInInventoryIfNeeded(ada);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -2128,6 +2152,7 @@
sensorUuid));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2462,6 +2487,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 +2503,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 +2519,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..9b03afb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -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;
@@ -201,6 +202,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;
@@ -2881,7 +2883,7 @@
// IPC methods
///////////////////////////////////////////////////////////////////////////
/**
- * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+ * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes)
* @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
* List<AudioDeviceAttributes>)
*/
@@ -2891,8 +2893,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));
@@ -2948,7 +2953,7 @@
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -2963,6 +2968,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 +2997,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));
@@ -3023,7 +3034,7 @@
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -3036,7 +3047,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3060,7 +3072,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3076,7 +3089,7 @@
}
/**
- * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+ * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
*/
public int setPreferredDevicesForCapturePreset(
int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3095,6 +3108,8 @@
return AudioSystem.ERROR;
}
+ devices = retrieveBluetoothAddresses(devices);
+
final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
capturePreset, devices);
if (status != AudioSystem.SUCCESS) {
@@ -3141,7 +3156,7 @@
status, capturePreset));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -3155,7 +3170,8 @@
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/**
@@ -3175,7 +3191,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 +3203,8 @@
*/
public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
@NonNull AudioAttributes attributes) {
- return getDevicesForAttributesInt(attributes, false /* forVolume */);
+ return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+ getDevicesForAttributesInt(attributes, false /* forVolume */)));
}
/**
@@ -7390,6 +7409,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 +7494,8 @@
// verify parameters
Objects.requireNonNull(device);
+ device = retrieveBluetoothAddress(device);
+
return getDeviceVolumeBehaviorInt(device);
}
@@ -7547,9 +7570,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 +7616,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 +10451,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 +10595,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 +10616,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 +10637,7 @@
super.getMutingExpectedDevice_enforcePermission();
synchronized (mMuteAwaitConnectionLock) {
- return mMutingExpectedDevice;
+ return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
}
}
@@ -10510,6 +10646,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 +10658,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 +10668,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 +10689,7 @@
super.registerMuteAwaitConnectionDispatcher_enforcePermission();
if (register) {
- mMuteAwaitConnectionDispatchers.register(cb);
+ mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
} else {
mMuteAwaitConnectionDispatchers.unregister(cb);
}
@@ -10568,8 +10713,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 +10740,8 @@
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
}
- dispatchMuteAwaitConnection(cb -> { try {
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
timedOutDevice, mutedUsages);
@@ -10597,13 +10749,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 +13396,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 +13419,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 +13449,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/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/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/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/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..7c32cde 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();
}
@@ -2521,13 +2516,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
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/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 4922851..42a97f7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -29,6 +29,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Context;
@@ -214,10 +215,13 @@
ArchiveState createArchiveStateInternal(String packageName, int userId,
List<LauncherActivityInfo> mainActivities, String installerPackage)
throws IOException {
+ final int iconSize = mContext.getSystemService(
+ ActivityManager.class).getLauncherLargeIconSize();
+
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
- Path iconPath = storeIcon(packageName, mainActivity, userId, i);
+ Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
mainActivity.getLabel().toString(),
@@ -247,7 +251,7 @@
@VisibleForTesting
Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
- @UserIdInt int userId, int index) throws IOException {
+ @UserIdInt int userId, int index, int iconSize) throws IOException {
int iconResourceId = mainActivity.getActivityInfo().getIconResource();
if (iconResourceId == 0) {
// The app doesn't define an icon. No need to store anything.
@@ -255,7 +259,7 @@
}
File iconsDir = createIconsDir(userId);
File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
- Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
+ Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
try (FileOutputStream out = new FileOutputStream(iconFile)) {
// Note: Quality is ignored for PNGs.
if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
@@ -590,8 +594,8 @@
/**
* Creates serializable archived activities from launcher activities.
*/
- static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
- throws IOException {
+ static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos,
+ int iconSize) throws IOException {
if (infos == null || infos.isEmpty()) {
throw new IllegalArgumentException("No launcher activities");
}
@@ -606,7 +610,7 @@
archivedActivity.title = info.getLabel().toString();
archivedActivity.originalComponentName = info.getComponentName();
archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null :
- bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0)));
+ bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize));
// TODO(b/298452477) Handle monochrome icons.
archivedActivity.monochromeIconBitmap = null;
activities.add(archivedActivity);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 61b6b24..6b4ac5b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -215,7 +215,6 @@
import com.android.server.pm.dex.ArtUtils;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.dex.ViewCompiler;
import com.android.server.pm.local.PackageManagerLocalImpl;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
@@ -812,8 +811,6 @@
private final DexManager mDexManager;
private final DynamicCodeLogger mDynamicCodeLogger;
- final ViewCompiler mViewCompiler;
-
private final AtomicInteger mNextMoveId = new AtomicInteger();
final MovePackageHelper.MoveCallbacks mMoveCallbacks;
@@ -1486,11 +1483,14 @@
archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
archiveState);
} else {
+ final int iconSize = mContext.getSystemService(
+ ActivityManager.class).getLauncherLargeIconSize();
+
var mainActivities =
mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
userId);
archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
- mainActivities);
+ mainActivities, iconSize);
}
} catch (Exception e) {
throw new IllegalArgumentException("Package does not have a main activity", e);
@@ -1670,7 +1670,6 @@
(i, pm) -> new ArtManagerService(i.getContext(), i.getInstaller(),
i.getInstallLock()),
(i, pm) -> ApexManager.getInstance(),
- (i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()),
(i, pm) -> (IncrementalManager)
i.getContext().getSystemService(Context.INCREMENTAL_SERVICE),
(i, pm) -> new DefaultAppProvider(() -> context.getSystemService(RoleManager.class),
@@ -1881,7 +1880,6 @@
mProcessLoggingHandler = testParams.processLoggingHandler;
mProtectedPackages = testParams.protectedPackages;
mSeparateProcesses = testParams.separateProcesses;
- mViewCompiler = testParams.viewCompiler;
mRequiredVerifierPackages = testParams.requiredVerifierPackages;
mRequiredInstallerPackage = testParams.requiredInstallerPackage;
mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
@@ -2044,7 +2042,6 @@
mBackgroundDexOptService = injector.getBackgroundDexOptService();
mArtManagerService = injector.getArtManagerService();
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
- mViewCompiler = injector.getViewCompiler();
mSharedLibraries = mInjector.getSharedLibrariesImpl();
mBackgroundHandler = injector.getBackgroundHandler();
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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 154ee6e..bb55a39 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5095,9 +5095,9 @@
}
}
- 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
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/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 88c2e09..dd39fb0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -34,7 +34,7 @@
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.StatsEvent;
import com.android.internal.annotations.GuardedBy;
@@ -256,10 +256,11 @@
@VisibleForTesting
final class MyUidObserver extends UidObserver {
- private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
-
+ private final Object mCacheLock = new Object();
+ @GuardedBy("mCacheLock")
+ private final SparseIntArray mProcStatesCache = new SparseIntArray();
public boolean isUidForeground(int uid) {
- synchronized (mLock) {
+ synchronized (mCacheLock) {
return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
<= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
}
@@ -268,6 +269,9 @@
@Override
public void onUidGone(int uid, boolean disabled) {
FgThread.getHandler().post(() -> {
+ synchronized (mCacheLock) {
+ mProcStatesCache.delete(uid);
+ }
synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
@@ -280,7 +284,6 @@
sessionSet.valueAt(j).close();
}
}
- mProcStatesCache.delete(uid);
}
});
}
@@ -292,15 +295,18 @@
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
FgThread.getHandler().post(() -> {
- synchronized (mLock) {
+ synchronized (mCacheLock) {
mProcStatesCache.put(uid, procState);
+ }
+ boolean shouldAllowUpdate = isUidForeground(uid);
+ synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
return;
}
for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
for (AppHintSession s : sessionSet) {
- s.onProcStateChanged();
+ s.onProcStateChanged(shouldAllowUpdate);
}
}
}
@@ -429,10 +435,10 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
+ pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
+ pw.println("HAL Support: " + isHalSupported());
+ pw.println("Active Sessions:");
synchronized (mLock) {
- pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
- pw.println("HAL Support: " + isHalSupported());
- pw.println("Active Sessions:");
for (int i = 0; i < mActiveSessions.size(); i++) {
pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
@@ -476,7 +482,8 @@
mHalSessionPtr = halSessionPtr;
mTargetDurationNanos = durationNanos;
mUpdateAllowed = true;
- updateHintAllowed();
+ final boolean allowed = mUidObserver.isUidForeground(mUid);
+ updateHintAllowed(allowed);
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -486,9 +493,8 @@
}
@VisibleForTesting
- boolean updateHintAllowed() {
- synchronized (mLock) {
- final boolean allowed = mUidObserver.isUidForeground(mUid);
+ boolean updateHintAllowed(boolean allowed) {
+ synchronized (this) {
if (allowed && !mUpdateAllowed) resume();
if (!allowed && mUpdateAllowed) pause();
mUpdateAllowed = allowed;
@@ -498,8 +504,8 @@
@Override
public void updateTargetWorkDuration(long targetDurationNanos) {
- synchronized (mLock) {
- if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ synchronized (this) {
+ if (mHalSessionPtr == 0 || !mUpdateAllowed) {
return;
}
Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
@@ -511,8 +517,8 @@
@Override
public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
- synchronized (mLock) {
- if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ synchronized (this) {
+ if (mHalSessionPtr == 0 || !mUpdateAllowed) {
return;
}
Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
@@ -534,11 +540,13 @@
/** TODO: consider monitor session threads and close session if any thread is dead. */
@Override
public void close() {
- synchronized (mLock) {
+ synchronized (this) {
if (mHalSessionPtr == 0) return;
mNativeWrapper.halCloseHintSession(mHalSessionPtr);
mHalSessionPtr = 0;
mToken.unlinkToDeath(this, 0);
+ }
+ synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
if (tokenMap == null) {
Slogf.w(TAG, "UID %d is not present in active session map", mUid);
@@ -557,8 +565,8 @@
@Override
public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
- synchronized (mLock) {
- if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ synchronized (this) {
+ if (mHalSessionPtr == 0 || !mUpdateAllowed) {
return;
}
Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
@@ -568,7 +576,7 @@
}
public void setThreads(@NonNull int[] tids) {
- synchronized (mLock) {
+ synchronized (this) {
if (mHalSessionPtr == 0) {
return;
}
@@ -588,7 +596,7 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
- if (!updateHintAllowed()) {
+ if (!mUpdateAllowed) {
Slogf.v(TAG, "update hint not allowed, storing tids.");
mNewThreadIds = tids;
return;
@@ -599,13 +607,15 @@
}
public int[] getThreadIds() {
- return mThreadIds;
+ synchronized (this) {
+ return Arrays.copyOf(mThreadIds, mThreadIds.length);
+ }
}
@Override
public void setMode(int mode, boolean enabled) {
- synchronized (mLock) {
- if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ synchronized (this) {
+ if (mHalSessionPtr == 0 || !mUpdateAllowed) {
return;
}
Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
@@ -614,19 +624,19 @@
}
}
- private void onProcStateChanged() {
- updateHintAllowed();
+ private void onProcStateChanged(boolean updateAllowed) {
+ updateHintAllowed(updateAllowed);
}
private void pause() {
- synchronized (mLock) {
+ synchronized (this) {
if (mHalSessionPtr == 0) return;
mNativeWrapper.halPauseHintSession(mHalSessionPtr);
}
}
private void resume() {
- synchronized (mLock) {
+ synchronized (this) {
if (mHalSessionPtr == 0) return;
mNativeWrapper.halResumeHintSession(mHalSessionPtr);
if (mNewThreadIds != null) {
@@ -638,12 +648,12 @@
}
private void dump(PrintWriter pw, String prefix) {
- synchronized (mLock) {
+ synchronized (this) {
pw.println(prefix + "SessionPID: " + mPid);
pw.println(prefix + "SessionUID: " + mUid);
pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
- pw.println(prefix + "SessionAllowed: " + updateHintAllowed());
+ pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
}
}
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
new file mode 100644
index 0000000..10c5362
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.power.hint"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index e97bda2..1e4b258 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
@@ -205,9 +207,130 @@
backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
}
+ private class BalState {
+
+ private final String mCallingPackage;
+ private final int mCallingUid;
+ private final int mCallingPid;
+ private final @ActivityTaskManagerService.AppSwitchState int mAppSwitchState;
+ private final boolean mCallingUidHasAnyVisibleWindow;
+ private final @ActivityManager.ProcessState int mCallingUidProcState;
+ private final boolean mIsCallingUidPersistentSystemProcess;
+ private final BackgroundStartPrivileges mBalAllowedByPiSender;
+ private final BackgroundStartPrivileges mBalAllowedByPiCreator;
+ private final String mRealCallingPackage;
+ private final int mRealCallingUid;
+ private final int mRealCallingPid;
+ private final boolean mRealCallingUidHasAnyVisibleWindow;
+ private final @ActivityManager.ProcessState int mRealCallingUidProcState;
+ private final boolean mIsRealCallingUidPersistentSystemProcess;
+ private final PendingIntentRecord mOriginatingPendingIntent;
+ private final BackgroundStartPrivileges mBackgroundStartPrivileges;
+ private final Intent mIntent;
+ private final WindowProcessController mCallerApp;
+ private final WindowProcessController mRealCallerApp;
+
+ private BalState(int callingUid, int callingPid, final String callingPackage,
+ int realCallingUid, int realCallingPid,
+ WindowProcessController callerApp,
+ PendingIntentRecord originatingPendingIntent,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ Intent intent,
+ ActivityOptions checkedOptions) {
+ this.mCallingPackage = callingPackage;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ mRealCallingUid = realCallingUid;
+ mRealCallingPid = realCallingPid;
+ mCallerApp = callerApp;
+ mBackgroundStartPrivileges = backgroundStartPrivileges;
+ mOriginatingPendingIntent = originatingPendingIntent;
+ mIntent = intent;
+ mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+ mBalAllowedByPiSender =
+ PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(checkedOptions,
+ realCallingUid, mRealCallingPackage);
+ mBalAllowedByPiCreator =
+ checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL;
+ mAppSwitchState = mService.getBalAppSwitchesState();
+ mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
+ mIsCallingUidPersistentSystemProcess =
+ mCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+ mCallingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
+ if (callingUid == realCallingUid) {
+ mRealCallingUidProcState = mCallingUidProcState;
+ mRealCallingUidHasAnyVisibleWindow = mCallingUidHasAnyVisibleWindow;
+ // In the PendingIntent case callerApp is not passed in, so resolve it ourselves.
+ mRealCallerApp = callerApp == null
+ ? mService.getProcessController(realCallingPid, realCallingUid)
+ : callerApp;
+ mIsRealCallingUidPersistentSystemProcess = mIsCallingUidPersistentSystemProcess;
+ } else {
+ mRealCallingUidProcState = mService.mActiveUids.getUidState(realCallingUid);
+ mRealCallingUidHasAnyVisibleWindow =
+ mService.hasActiveVisibleWindow(realCallingUid);
+ mRealCallerApp = mService.getProcessController(realCallingPid, realCallingUid);
+ mIsRealCallingUidPersistentSystemProcess =
+ mRealCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+ }
+ }
+
+ private String getDebugPackageName(String packageName, int uid) {
+ if (packageName != null) {
+ return packageName; // use actual package
+ }
+ if (uid == 0) {
+ return "root[debugOnly]";
+ }
+ String name = mService.mContext.getPackageManager().getNameForUid(uid);
+ if (name == null) {
+ name = "uid=" + uid;
+ }
+ return name + "[debugOnly]";
+ }
+
+ private String dump(@BalCode int mResultIfPiCreatorAllowsBal,
+ @BalCode int mResultIfPiSenderAllowsBal) {
+ return " [callingPackage: " + getDebugPackageName(mCallingPackage, mCallingUid)
+ + "; callingUid: " + mCallingUid
+ + "; appSwitchState: " + mAppSwitchState
+ + "; callingUidHasAnyVisibleWindow: " + mCallingUidHasAnyVisibleWindow
+ + "; callingUidProcState: " + DebugUtils.valueToString(
+ ActivityManager.class, "PROCESS_STATE_", mCallingUidProcState)
+ + "; isCallingUidPersistentSystemProcess: "
+ + mIsCallingUidPersistentSystemProcess
+ + "; balAllowedByPiCreator: " + mBalAllowedByPiCreator
+ + "; balAllowedByPiSender: " + mBalAllowedByPiSender
+ + "; realCallingPackage: "
+ + getDebugPackageName(mRealCallingPackage, mRealCallingUid)
+ + "; realCallingUid: " + mRealCallingUid
+ + "; realCallingPid: " + mRealCallingPid
+ + "; realCallingUidHasAnyVisibleWindow: " + mRealCallingUidHasAnyVisibleWindow
+ + "; realCallingUidProcState: " + DebugUtils.valueToString(
+ ActivityManager.class, "PROCESS_STATE_", mRealCallingUidProcState)
+ + "; isRealCallingUidPersistentSystemProcess: "
+ + mIsRealCallingUidPersistentSystemProcess
+ + "; originatingPendingIntent: " + mOriginatingPendingIntent
+ + "; backgroundStartPrivileges: " + mBackgroundStartPrivileges
+ + "; intent: " + mIntent
+ + "; callerApp: " + mCallerApp
+ + "; realCallerApp: " + mRealCallerApp
+ + "; inVisibleTask: "
+ + (mCallerApp != null && mCallerApp.hasActivityInVisibleTask())
+ + "; realInVisibleTask: "
+ + (mRealCallerApp != null && mRealCallerApp.hasActivityInVisibleTask())
+ + "; resultIfPiSenderAllowsBal: " + balCodeToString(mResultIfPiSenderAllowsBal)
+ + "; resultIfPiCreatorAllowsBal: "
+ + balCodeToString(mResultIfPiCreatorAllowsBal)
+ + "]";
+ }
+ }
+
/**
* @return A code denoting which BAL rule allows an activity to be started,
- * or {@link BAL_BLOCK} if the launch should be blocked
+ * or {@link #BAL_BLOCK} if the launch should be blocked
*/
@BalCode
int checkBackgroundActivityStart(
@@ -221,36 +344,136 @@
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)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
+ /*background*/ false, state,
+ "uid in SDK sandbox has visible (non-toast) window");
+ }
+ }
+
+ @BalCode int resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
+ @BalCode int resultForRealCaller = callingUid == realCallingUid
+ ? resultForCaller // no need to calculate again
+ : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+
+ if (resultForCaller != BAL_BLOCK
+ && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start explicitly allowed by PI creator. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ return resultForCaller;
+ }
+ if (resultForRealCaller != BAL_BLOCK
+ && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.i(TAG, "Activity start explicitly allowed by PI sender. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ return resultForRealCaller;
+ }
+ if (resultForCaller != BAL_BLOCK
+ && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ // Allowed before V by creator
+ Slog.wtf(TAG,
+ "With Android 15 BAL hardening this activity start would be blocked"
+ + " (missing opt in by PI creator)! "
+ + state.dump(resultForCaller, resultForRealCaller));
+ return resultForCaller;
+ }
+ if (resultForRealCaller != BAL_BLOCK
+ && checkedOptions.getPendingIntentBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ // Allowed before U by sender
+ if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
+ Slog.wtf(TAG,
+ "With Android 14 BAL hardening this activity start would be blocked"
+ + " (missing opt in by PI sender)! "
+ + state.dump(resultForCaller, resultForRealCaller));
+ return resultForRealCaller;
+ }
+ Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
+ + " (missing opt in by PI sender)! "
+ + state.dump(resultForCaller, resultForRealCaller));
+ // fall through
+ }
+ // anything that has fallen through would currently be aborted
+ Slog.w(TAG, "Background activity launch blocked"
+ + state.dump(resultForCaller, resultForRealCaller));
+ // log aborted activity start to TRON
+ if (mService.isActivityStartsLoggingEnabled()) {
+ mSupervisor
+ .getActivityMetricsLogger()
+ .logAbortedBgActivityStart(
+ intent,
+ callerApp,
+ callingUid,
+ callingPackage,
+ state.mCallingUidProcState,
+ state.mCallingUidHasAnyVisibleWindow,
+ realCallingUid,
+ state.mRealCallingUidProcState,
+ state.mRealCallingUidHasAnyVisibleWindow,
+ (originatingPendingIntent != null));
+ }
+ return BAL_BLOCK;
+ }
+
+ /**
+ * @return A code denoting which BAL rule allows an activity to be started,
+ * or {@link #BAL_BLOCK} if the launch should be blocked
+ */
+ @BalCode
+ int checkBackgroundActivityStartAllowedByCaller(BalState state) {
+ int callingUid = state.mCallingUid;
+ int callingPid = state.mCallingPid;
+ final String callingPackage = state.mCallingPackage;
+ WindowProcessController callerApp = state.mCallerApp;
+
// don't abort for the most important UIDs
final int callingAppId = UserHandle.getAppId(callingUid);
- 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 logStartAllowedAndReturnCode(
+ BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
+ state, "Important callingUid");
+ }
- // Always allow home application to start activities.
- if (isHomeApp(callingUid, callingPackage)) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false, callingUid, realCallingUid, intent,
- "Home app");
- }
+ // Always allow home application to start activities.
+ if (isHomeApp(callingUid, callingPackage)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ false, state,
+ "Home app");
+ }
- // IME should always be allowed to start activity, like IME settings.
- final WindowState imeWindow =
- mService.mRootWindowContainer.getCurrentInputMethodWindow();
- if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false, 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 logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ false, state,
+ "Active ime");
}
// This is used to block background activity launch even if the app is still
@@ -269,329 +492,171 @@
appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
final boolean allowCallingUidStartActivity =
((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
- && callingUidHasAnyVisibleWindow)
+ && callingUidHasAnyVisibleWindow)
|| isCallingUidPersistentSystemProcess;
- if (useCallingUidState && allowCallingUidStartActivity) {
+ if (allowCallingUidStartActivity) {
return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, callingUid, realCallingUid, intent,
+ /*background*/ false, state,
"callingUidHasAnyVisibleWindow = "
+ callingUid
+ ", isCallingUidPersistentSystemProcess = "
+ isCallingUidPersistentSystemProcess);
}
- // take realCallingUid into consideration
- final int realCallingUidProcState =
- (callingUid == realCallingUid)
- ? callingUidProcState
- : mService.mActiveUids.getUidState(realCallingUid);
- final boolean realCallingUidHasAnyVisibleWindow =
- (callingUid == realCallingUid)
- ? callingUidHasAnyVisibleWindow
- : mService.hasActiveVisibleWindow(realCallingUid);
- final int realCallingAppId = UserHandle.getAppId(realCallingUid);
- final boolean isRealCallingUidPersistentSystemProcess =
- (callingUid == realCallingUid)
- ? isCallingUidPersistentSystemProcess
- : (realCallingAppId == Process.SYSTEM_UID)
- || realCallingUidProcState
- <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
- // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
- // visible window.
- if (Process.isSdkSandboxUid(realCallingUid)) {
- int realCallingSdkSandboxUidToAppUid =
- Process.getAppUidForSdkSandboxUid(realCallingUid);
-
- if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
- /*background*/ false, callingUid, realCallingUid, intent,
- "uid in SDK sandbox has visible (non-toast) window");
- }
- }
-
- String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
-
- // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
- // The options here are the options passed by the sender and not those on the intent.
- final BackgroundStartPrivileges balAllowedByPiSender =
- PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- checkedOptions, realCallingUid, realCallingPackage);
-
- final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null
- || checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
- final boolean considerPiRules = logVerdictChangeByPiDefaultChange
- || balAllowedByPiSender.allowsBackgroundActivityStarts();
- final String verdictLogForPiSender =
- balAllowedByPiSender.allowsBackgroundActivityStarts() ? VERDICT_ALLOWED
- : VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL;
-
- @BalCode int resultIfPiSenderAllowsBal = BAL_BLOCK;
- if (realCallingUid != callingUid && considerPiRules) {
- resultIfPiSenderAllowsBal = checkPiBackgroundActivityStart(callingUid, realCallingUid,
- backgroundStartPrivileges, intent, checkedOptions,
- realCallingUidHasAnyVisibleWindow, isRealCallingUidPersistentSystemProcess,
- verdictLogForPiSender);
- }
- if (resultIfPiSenderAllowsBal != BAL_BLOCK
- && balAllowedByPiSender.allowsBackgroundActivityStarts()
- && !logVerdictChangeByPiDefaultChange) {
- // The result is to allow (because the sender allows BAL) and we are not interested in
- // logging differences, so just return.
- return resultIfPiSenderAllowsBal;
- }
- if (useCallingUidState) {
- // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
- if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
- callingPid, callingUid) == PERMISSION_GRANTED) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid, intent,
+ // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
+ if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
+ callingPid, callingUid) == PERMISSION_GRANTED) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
+ /*background*/ true, state,
"START_ACTIVITIES_FROM_BACKGROUND permission granted");
- }
- // don't abort if the caller has the same uid as the recents component
- if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
- return logStartAllowedAndReturnCode(
- BAL_ALLOW_ALLOWLISTED_COMPONENT,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid,
- intent, "Recents Component");
- }
- // don't abort if the callingUid is the device owner
- if (mService.isDeviceOwner(callingUid)) {
- return logStartAllowedAndReturnCode(
- BAL_ALLOW_ALLOWLISTED_COMPONENT,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid,
- intent, "Device Owner");
- }
- // don't abort if the callingUid is a affiliated profile owner
- if (mService.isAffiliatedProfileOwner(callingUid)) {
- return logStartAllowedAndReturnCode(
- BAL_ALLOW_ALLOWLISTED_COMPONENT,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid,
- intent, "Affiliated Profile Owner");
- }
- // don't abort if the callingUid has companion device
- final int callingUserId = UserHandle.getUserId(callingUid);
- if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
- return logStartAllowedAndReturnCode(
- BAL_ALLOW_ALLOWLISTED_COMPONENT,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid,
- intent, "Companion App");
- }
- // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
- if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
- Slog.w(
- TAG,
- "Background activity start for "
- + callingPackage
- + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
- return logStartAllowedAndReturnCode(
- BAL_ALLOW_SAW_PERMISSION,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid,
- intent, "SYSTEM_ALERT_WINDOW permission is granted");
- }
- // don't abort if the callingUid and callingPackage have the
- // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
- if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
- AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
- callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
- return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
- resultIfPiSenderAllowsBal, balAllowedByPiSender,
- /*background*/ true, callingUid, realCallingUid, intent,
- "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
- }
}
+ // don't abort if the caller has the same uid as the recents component
+ if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, state, "Recents Component");
+ }
+ // don't abort if the callingUid is the device owner
+ if (mService.isDeviceOwner(callingUid)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, state, "Device Owner");
+ }
+ // don't abort if the callingUid is a affiliated profile owner
+ if (mService.isAffiliatedProfileOwner(callingUid)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, state, "Affiliated Profile Owner");
+ }
+ // don't abort if the callingUid has companion device
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ /*background*/ true, state, "Companion App");
+ }
+ // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
+ if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
+ Slog.w(
+ TAG,
+ "Background activity start for "
+ + callingPackage
+ + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
+ return logStartAllowedAndReturnCode(BAL_ALLOW_SAW_PERMISSION,
+ /*background*/ true, state, "SYSTEM_ALERT_WINDOW permission is granted");
+ }
+ // don't abort if the callingUid and callingPackage have the
+ // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
+ if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+ callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
+ return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION,
+ /*background*/ true, state,
+ "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
+ }
+
// If we don't have callerApp at this point, no caller was provided to startActivity().
// That's the case for PendingIntent-based starts, since the creator's process might not be
- // up and alive. 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.
+ @BalCode int callerAppAllowsBal = checkProcessAllowsBal(callerApp, state);
+ if (callerAppAllowsBal != BAL_BLOCK) {
+ 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));
- }
+ // If we are here, it means all exemptions based on the creator failed
return BAL_BLOCK;
}
- private @BalCode int checkPiBackgroundActivityStart(int callingUid, int realCallingUid,
- BackgroundStartPrivileges backgroundStartPrivileges, Intent intent,
- ActivityOptions checkedOptions, boolean realCallingUidHasAnyVisibleWindow,
- boolean isRealCallingUidPersistentSystemProcess, String verdictLog) {
- final boolean useCallerPermission =
- PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions);
- if (useCallerPermission
+ /**
+ * @return A code denoting which BAL rule allows an activity to be started,
+ * or {@link #BAL_BLOCK} if the launch should be blocked
+ */
+ @BalCode
+ int checkBackgroundActivityStartAllowedBySender(
+ BalState state,
+ ActivityOptions checkedOptions) {
+ int realCallingUid = state.mRealCallingUid;
+
+ if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
&& ActivityManager.checkComponentPermission(
- android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+ android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
realCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
- "realCallingUid has BAL permission. realCallingUid: " + realCallingUid,
- verdictLog);
+ /*background*/ false, state,
+ "realCallingUid has BAL permission. realCallingUid: " + realCallingUid);
}
// don't abort if the realCallingUid has a visible window
// TODO(b/171459802): We should check appSwitchAllowed also
- if (realCallingUidHasAnyVisibleWindow) {
+ if (state.mRealCallingUidHasAnyVisibleWindow) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
+ /*background*/ false, state,
"realCallingUid has visible (non-toast) window. realCallingUid: "
- + realCallingUid, verdictLog);
+ + realCallingUid);
}
// if the realCallingUid is a persistent system process, abort if the IntentSender
// wasn't allowed to start an activity
- if (isRealCallingUidPersistentSystemProcess
- && backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+ if (state.mIsRealCallingUidPersistentSystemProcess
+ && state.mBackgroundStartPrivileges.allowsBackgroundActivityStarts()) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
+ /*background*/ false, state,
"realCallingUid is persistent system process AND intent "
+ "sender allowed (allowBackgroundActivityStart = true). "
- + "realCallingUid: " + realCallingUid, verdictLog);
+ + "realCallingUid: " + realCallingUid);
}
// don't abort if the realCallingUid is an associated companion app
if (mService.isAssociatedCompanionApp(
UserHandle.getUserId(realCallingUid), realCallingUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
- /*background*/ false, callingUid, realCallingUid, intent,
+ /*background*/ false, state,
"realCallingUid is a companion app. "
- + "realCallingUid: " + realCallingUid, verdictLog);
+ + "realCallingUid: " + realCallingUid);
+ }
+
+ // don't abort if the callerApp or other processes of that uid are allowed in any way
+ @BalCode int realCallerAppAllowsBal =
+ checkProcessAllowsBal(state.mRealCallerApp, state);
+ if (realCallerAppAllowsBal != BAL_BLOCK) {
+ return realCallerAppAllowsBal;
+ }
+
+ // If we are here, it means all exemptions based on PI sender failed
+ return BAL_BLOCK;
+ }
+
+ /**
+ * Check if the app allows BAL.
+ *
+ * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
+ * String, int, boolean, boolean, boolean, long, long, long)} for details on the
+ * exceptions.
+ */
+ private @BalCode int checkProcessAllowsBal(WindowProcessController app, BalState state) {
+ if (app == null) {
+ return BAL_BLOCK;
+ }
+ // first check the original calling process
+ final @BalCode int balAllowedForCaller = app
+ .areBackgroundActivityStartsAllowed(state.mAppSwitchState);
+ if (balAllowedForCaller != BAL_BLOCK) {
+ return logStartAllowedAndReturnCode(balAllowedForCaller,
+ /*background*/ true, state,
+ "callerApp process (pid = " + app.getPid()
+ + ", uid = " + app.mUid + ") is allowed");
+ } else {
+ // only if that one wasn't allowed, check the other ones
+ final ArraySet<WindowProcessController> uidProcesses =
+ mService.mProcessMap.getProcesses(app.mUid);
+ if (uidProcesses != null) {
+ for (int i = uidProcesses.size() - 1; i >= 0; i--) {
+ final WindowProcessController proc = uidProcesses.valueAt(i);
+ int balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
+ state.mAppSwitchState);
+ if (proc != app && balAllowedForUid != BAL_BLOCK) {
+ return logStartAllowedAndReturnCode(balAllowedForUid,
+ /*background*/ true, state,
+ "process" + proc.getPid() + " from uid " + app.mUid
+ + " is allowed");
+ }
+ }
+ }
}
return BAL_BLOCK;
}
@@ -1091,51 +1156,32 @@
return joiner.toString();
}
- static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
- int callingUid, int realCallingUid, Intent intent, int pid, String msg) {
- return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
- DEBUG_ACTIVITY_STARTS ? ("[Process(" + pid + ")]" + msg) : "");
+ static @BalCode int logStartAllowedAndReturnCode(@BalCode int code,
+ boolean background, int callingUid, int realCallingUid, Intent intent, int pid,
+ String msg) {
+ return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid,
+ intent, DEBUG_ACTIVITY_STARTS ? ("[Process(" + pid + ")]" + msg) : "");
}
- static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
- int callingUid, int realCallingUid, Intent intent, String msg) {
- return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
- msg, VERDICT_ALLOWED);
+ private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code,
+ boolean background, BalState state, String msg) {
+ return logStartAllowedAndReturnCode(code, background, state.mCallingUid,
+ state.mRealCallingUid, state.mIntent, msg);
}
- /**
- * Logs the start and returns one of the provided codes depending on if the PI sender allows
- * using its BAL privileges.
- */
- static @BalCode int logStartAllowedAndReturnCode(@BalCode int result,
- @BalCode int resultIfPiSenderAllowsBal, BackgroundStartPrivileges balAllowedByPiSender,
+ private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code,
boolean background, int callingUid, int realCallingUid, Intent intent, String msg) {
- if (resultIfPiSenderAllowsBal != BAL_BLOCK
- && balAllowedByPiSender.allowsBackgroundActivityStarts()) {
- // resultIfPiSenderAllowsBal was already logged, so just return
- return resultIfPiSenderAllowsBal;
- }
- return logStartAllowedAndReturnCode(result, background, callingUid, realCallingUid,
- intent, msg, VERDICT_ALLOWED);
- }
-
-
- static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
- int callingUid, int realCallingUid, Intent intent, String msg, String verdict) {
statsLogBalAllowed(code, callingUid, realCallingUid, intent);
if (DEBUG_ACTIVITY_STARTS) {
StringBuilder builder = new StringBuilder();
if (background) {
builder.append("Background ");
}
- builder.append(verdict + ": " + msg + ". callingUid: " + callingUid + ". ");
+ builder.append("Activity start allowed: " + msg + ". callingUid: "
+ + callingUid + ". ");
builder.append("BAL Code: ");
builder.append(balCodeToString(code));
- if (verdict.equals(VERDICT_ALLOWED)) {
- Slog.i(TAG, builder.toString());
- } else {
- Slog.d(TAG, builder.toString());
- }
+ Slog.i(TAG, builder.toString());
}
return code;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4fa6e29..e7893da 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;
}
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..d2f6d16 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4715,7 +4715,7 @@
}
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();
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/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/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index bded9b4..809a0e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -919,6 +919,7 @@
assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
mSetFlagsRule.disableFlags(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER);
when(mService.isBatteryCharging()).thenReturn(false);
+ when(mService.isBatteryNotLow()).thenReturn(false);
when(mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota(
any(), eq(NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS)))
@@ -938,6 +939,12 @@
assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
+ assertFalse(controller.isSatisfied(latePrefetch, net, caps, mConstants));
+ // Only relax restrictions when we at least know the estimated download bytes.
+ assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
+ assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
+
+ when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants));
// Only relax restrictions when we at least know the estimated download bytes.
assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 7a6ac4e..e7f1d16e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
@@ -100,6 +101,8 @@
@Mock
private LauncherApps mLauncherApps;
@Mock
+ private ActivityManager mActivityManager;
+ @Mock
private PackageManager mPackageManager;
@Mock
private PackageInstallerService mInstallerService;
@@ -159,6 +162,10 @@
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
+
+ when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+ when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+
doReturn(mComputer).when(mPackageManagerService).snapshotComputer();
when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
Binder.getCallingUid());
@@ -172,7 +179,7 @@
mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService));
doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
doReturn(mIcon).when(mArchiveManager).decodeIcon(
any(ArchiveState.ArchiveActivityInfo.class));
}
@@ -277,7 +284,7 @@
public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
IOException e = new IOException("IO");
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
diff --git a/services/tests/servicestests/src/com/android/server/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/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 9fca513..d09aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -25,8 +25,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -46,6 +44,7 @@
import android.os.IHintSession;
import android.os.PerformanceHintManager;
import android.os.Process;
+import android.util.Log;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -58,7 +57,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
/**
* Tests for {@link com.android.server.power.hint.HintManagerService}.
@@ -67,8 +73,11 @@
* atest FrameworksServicesTests:HintManagerServiceTest
*/
public class HintManagerServiceTest {
+ private static final String TAG = "HintManagerServiceTest";
+
private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L;
private static final long DEFAULT_TARGET_DURATION = 16666666L;
+ private static final long CONCURRENCY_TEST_DURATION_SEC = 10;
private static final int UID = Process.myUid();
private static final int TID = Process.myPid();
private static final int TGID = Process.getThreadGroupLeader(TID);
@@ -103,6 +112,55 @@
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
}
+ static class NativeWrapperFake extends NativeWrapper {
+ @Override
+ public void halInit() {
+ }
+
+ @Override
+ public long halGetHintSessionPreferredRate() {
+ return 1;
+ }
+
+ @Override
+ public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
+ return 1;
+ }
+
+ @Override
+ public void halPauseHintSession(long halPtr) {
+ }
+
+ @Override
+ public void halResumeHintSession(long halPtr) {
+ }
+
+ @Override
+ public void halCloseHintSession(long halPtr) {
+ }
+
+ @Override
+ public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
+ }
+
+ @Override
+ public void halReportActualWorkDuration(
+ long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
+ }
+
+ @Override
+ public void halSendHint(long halPtr, int hint) {
+ }
+
+ @Override
+ public void halSetThreads(long halPtr, int[] tids) {
+ }
+
+ @Override
+ public void halSetMode(long halPtr, int mode, boolean enabled) {
+ }
+ }
+
private HintManagerService createService() {
mService = new HintManagerService(mContext, new Injector() {
NativeWrapper createNativeWrapper() {
@@ -112,6 +170,15 @@
return mService;
}
+ private HintManagerService createServiceWithFakeWrapper() {
+ mService = new HintManagerService(mContext, new Injector() {
+ NativeWrapper createNativeWrapper() {
+ return new NativeWrapperFake();
+ }
+ });
+ return mService;
+ }
+
@Test
public void testInitializeService() {
HintManagerService service = createService();
@@ -166,8 +233,7 @@
latch.countDown();
});
latch.await();
-
- assumeFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
// Set session to foreground and calling updateHintAllowed() would invoke resume();
@@ -181,7 +247,7 @@
});
latch2.await();
- assumeTrue(a.updateHintAllowed());
+ assertTrue(service.mUidObserver.isUidForeground(a.mUid));
verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
}
@@ -254,7 +320,7 @@
});
latch.await();
- assumeFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
}
@@ -280,7 +346,7 @@
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
FgThread.getHandler().runWithScissors(() -> { }, 500);
- assertFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
}
@@ -303,7 +369,7 @@
});
latch.await();
- assertFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
}
@Test
@@ -316,7 +382,7 @@
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
- assertTrue(a.updateHintAllowed());
+ assertTrue(service.mUidObserver.isUidForeground(a.mUid));
}
@Test
@@ -342,7 +408,7 @@
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
FgThread.getHandler().runWithScissors(() -> { }, 500);
- assertFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
a.setThreads(SESSION_TIDS_A);
verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
}
@@ -372,9 +438,159 @@
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
FgThread.getHandler().runWithScissors(() -> { }, 500);
- assertFalse(a.updateHintAllowed());
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
a.setMode(0, true);
verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
}
+ // This test checks that concurrent operations from different threads on IHintService,
+ // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
+ // check the output of threads' reportActualDuration performance to detect lock starvation
+ // but the result is not stable, so it's better checked manually.
+ @Test
+ public void testConcurrency() throws Exception {
+ HintManagerService service = createServiceWithFakeWrapper();
+ // initialize session threads to run in parallel
+ final int sessionCount = 10;
+ // the signal that the main thread will send to session threads to check for run or exit
+ AtomicReference<Boolean> shouldRun = new AtomicReference<>(true);
+ // the signal for main test thread to wait for session threads and notifier thread to
+ // finish and exit
+ CountDownLatch latch = new CountDownLatch(sessionCount + 1);
+ // list of exceptions with one per session thread or notifier thread
+ List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1);
+ List<Thread> threads = new ArrayList<>(sessionCount + 1);
+ for (int i = 0; i < sessionCount; i++) {
+ final AtomicReference<Exception> exec = new AtomicReference<>();
+ execs.add(exec);
+ int j = i;
+ Thread app = new Thread(() -> {
+ try {
+ while (shouldRun.get()) {
+ runAppHintSession(service, j, shouldRun);
+ }
+ } catch (Exception e) {
+ exec.set(e);
+ } finally {
+ latch.countDown();
+ }
+ });
+ threads.add(app);
+ }
+
+ // initialize a UID state notifier thread to run in parallel
+ final AtomicReference<Exception> notifierExec = new AtomicReference<>();
+ execs.add(notifierExec);
+ Thread notifier = new Thread(() -> {
+ try {
+ long min = Long.MAX_VALUE;
+ long max = Long.MIN_VALUE;
+ long sum = 0;
+ int count = 0;
+ while (shouldRun.get()) {
+ long start = System.nanoTime();
+ service.mUidObserver.onUidStateChanged(UID,
+ ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+ long elapsed = System.nanoTime() - start;
+ sum += elapsed;
+ count++;
+ min = Math.min(min, elapsed);
+ max = Math.max(max, elapsed);
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+ service.mUidObserver.onUidStateChanged(UID,
+ ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+ }
+ Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count);
+ service.mUidObserver.onUidGone(UID, true);
+ } catch (Exception e) {
+ notifierExec.set(e);
+ } finally {
+ latch.countDown();
+ }
+ });
+ threads.add(notifier);
+
+ // start all the threads
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ // keep the test running for a few seconds
+ LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC));
+ // send signal to stop all threads
+ shouldRun.set(false);
+ // wait for all threads to exit
+ latch.await();
+ // check if any thread throws exception
+ for (AtomicReference<Exception> exec : execs) {
+ if (exec.get() != null) {
+ throw exec.get();
+ }
+ }
+ }
+
+ private void runAppHintSession(HintManagerService service, int logId,
+ AtomicReference<Boolean> shouldRun) throws Exception {
+ IBinder token = new Binder();
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+ // we will start some threads and get their valid TIDs to update
+ int threadCount = 3;
+ // the list of TIDs
+ int[] tids = new int[threadCount];
+ // atomic index for each thread to set its TID in the list
+ AtomicInteger k = new AtomicInteger(0);
+ // signal for the session main thread to wait for child threads to finish updating TIDs
+ CountDownLatch latch = new CountDownLatch(threadCount);
+ // signal for the session main thread to notify child threads to exit
+ CountDownLatch stopLatch = new CountDownLatch(1);
+ for (int j = 0; j < threadCount; j++) {
+ Thread thread = new Thread(() -> {
+ try {
+ tids[k.getAndIncrement()] = android.os.Process.myTid();
+ latch.countDown();
+ stopLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ thread.start();
+ }
+ latch.await();
+ a.setThreads(tids);
+ // we don't need the threads to exist after update
+ stopLatch.countDown();
+ a.updateTargetWorkDuration(5);
+ // measure the time it takes in HintManagerService to run reportActualDuration
+ long min = Long.MAX_VALUE;
+ long max = Long.MIN_VALUE;
+ long sum = 0;
+ int count = 0;
+ List<Long> values = new ArrayList<>();
+ long testStart = System.nanoTime();
+ // run report actual for 4-second per cycle
+ while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos(
+ Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) {
+ long start = System.nanoTime();
+ a.reportActualWorkDuration(new long[]{5}, new long[]{start});
+ long elapsed = System.nanoTime() - start;
+ values.add(elapsed);
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5));
+ sum += elapsed;
+ count++;
+ min = Math.min(min, elapsed);
+ max = Math.max(max, elapsed);
+ }
+ Collections.sort(values);
+ if (!values.isEmpty()) {
+ Log.d(TAG, "app thread " + logId + " min " + min + " max " + max
+ + " avg " + sum / count + " count " + count
+ + " 80th " + values.get((int) (values.size() * 0.8))
+ + " 90th " + values.get((int) (values.size() * 0.9))
+ + " 95th " + values.get((int) (values.size() * 0.95)));
+ } else {
+ Log.w(TAG, "No reportActualWorkDuration executed");
+ }
+ a.close();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 51b9c17..47f15b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -18,6 +18,7 @@
import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
+import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -486,6 +487,29 @@
}
@Test
+ public void testRepostAll() throws Exception {
+ final int profileId = 11;
+ final int otherUserId = 2;
+ IntArray userIds = new IntArray();
+ userIds.add(UserHandle.USER_SYSTEM);
+ userIds.add(profileId);
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.of(profileId));
+ NotificationRecord r4 = getNotificationRecord("pkg", 4, "four", UserHandle.of(otherUserId));
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ mSnoozeHelper.snooze(r4, 1000);
+
+ mSnoozeHelper.repostAll(userIds);
+
+ verify(mCallback, times(3)).repost(anyInt(), any(), anyBoolean());
+ // All notifications were reposted, except the one for otherUserId
+ assertThat(mSnoozeHelper.getSnoozed()).containsExactly(r4);
+ }
+
+ @Test
public void testGetSnoozedBy() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
diff --git a/services/tests/wmtests/src/com/android/server/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/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/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 6b47db1..e2cd4f8 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -469,6 +469,9 @@
* The satellite service should report the NTN signal strength via
* ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
*
+ * Note: This API can be invoked multiple times. If the modem is already in the expected
+ * state from a previous request, subsequent invocations may be ignored.
+ *
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned:
@@ -485,6 +488,9 @@
* be called when device is screen off to save power by not letting signal strength updates to
* wake up application processor.
*
+ * Note: This API can be invoked multiple times. If the modem is already in the expected
+ * state from a previous request, subsequent invocations may be ignored.
+ *
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned: